diff --git a/.gitignore b/.gitignore index c34a6aea..7df6cec2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ jspm_packages/ # dotenv environment variables file .env +.env.* # next.js build output .next diff --git a/.vscode/settings.json b/.vscode/settings.json index a519cb2d..439ea43f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,10 +8,12 @@ "AITEST", "Aliyun", "aweme", + "doubao", "douyin", "httpbin", "iconfont", "qwen", - "taobao" + "taobao", + "Volcengine" ] } diff --git a/apps/site/docs/en/faq.md b/apps/site/docs/en/faq.md index 7863fb12..fc39b160 100644 --- a/apps/site/docs/en/faq.md +++ b/apps/site/docs/en/faq.md @@ -2,9 +2,7 @@ ## Can Midscene smartly plan the actions according to my one-line goal? Like executing "Tweet 'hello world'" -Midscene is an automation assistance SDK with a key feature of action stability — ensuring the same actions are performed in each run. To maintain this stability, we encourage you to provide detailed instructions to help the AI understand each step of your task. - -If you require a 'goal-to-task' AI planning tool, you can develop one based on Midscene. +No. Midscene is an automation assistance SDK with a key feature of action stability — ensuring the same actions are performed in each run. To maintain this stability, we encourage you to provide detailed instructions to help the AI understand each step of your task. Related Docs: [Prompting Tips](./prompting-tips.html) @@ -16,11 +14,9 @@ There are some limitations with Midscene. We are still working on them. 2. LLM is not 100% stable. Even GPT-4o can't return the right answer all the time. Following the [Prompting Tips](./prompting-tips) will help improve stability. 3. Since we use JavaScript to retrieve items from the page, the elements inside the iframe cannot be accessed. -## Which LLM should I choose ? - -Midscene needs a multimodal Large Language Model (LLM) to understand the UI. Currently, we find that OpenAI's GPT-4o performs much better than others. +## Can I use a model other than `gpt-4o`? -You can [customize model and provider](./model-provider.html) if needed. +Yes. You can [customize model and provider](./model-provider.html) if needed. ## About the token cost diff --git a/apps/site/docs/en/model-provider.md b/apps/site/docs/en/model-provider.md index 2dcb71c6..a15adb6f 100644 --- a/apps/site/docs/en/model-provider.md +++ b/apps/site/docs/en/model-provider.md @@ -30,13 +30,36 @@ export MIDSCENE_OPENAI_INIT_CONFIG_JSON='{"baseURL":"....","defaultHeaders":{"ke export MIDSCENE_OPENAI_SOCKS_PROXY="socks5://127.0.0.1:1080" ``` -Note: +## Using Azure OpenAI Service -- Always choose a model that supports vision input. -- Currently, the known supported models are: `gpt-4o`, `qwen-vl-max-latest`, `gemini-1.5-pro` -- Please follow the terms of use of each model. +```bash +export MIDSCENE_USE_AZURE_OPENAI=1 +export MIDSCENE_AZURE_OPENAI_SCOPE="https://cognitiveservices.azure.com/.default" +export MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON='{"apiVersion": "2024-11-01-preview", "endpoint": "...", "deployment": "..."}' +``` + +## Choose a model other than `gpt-4o` + +We find that `gpt-4o` performs the best for Midscene at this moment. The other known supported models are: `gemini-1.5-pro`, `qwen-vl-max-latest`, `doubao-vision-pro-32k` + +If you want to use other models, please follow these steps: -## Example: Using `qwen-vl-max-latest` service from Aliyun +1. Choose a model that supports image input (a.k.a. multimodal model). +2. Find out how to to call it with an OpenAI SDK compatible endpoint. Usually you should set the `OPENAI_BASE_URL`, `OPENAI_API_KEY` and `MIDSCENE_MODEL_NAME`. +3. If you find it not working well after changing the model, you can try using some short and clear prompt (or roll back to the previous model). See more details in [Prompting Tips](./prompting-tips.html). +4. Remember to follow the terms of use of each model. + +## Example: Using `gemini-1.5-pro` from Google + +Configure the environment variables: + +```bash +export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai" +export OPENAI_API_KEY="....." +export MIDSCENE_MODEL_NAME="gemini-1.5-pro" +``` + +## Example: Using `qwen-vl-max-latest` from Aliyun Configure the environment variables: @@ -45,3 +68,15 @@ export OPENAI_API_KEY="sk-..." export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" export MIDSCENE_MODEL_NAME="qwen-vl-max-latest" ``` + +## Example: Using `doubao-vision-pro-32k` from Volcengine + +Create a inference point first: https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint + +Configure the environment variables: + +```bash +export OPENAI_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" +export OPENAI_API_KEY="..." +export MIDSCENE_MODEL_NAME="ep-202....." +``` diff --git a/apps/site/docs/zh/faq.md b/apps/site/docs/zh/faq.md index 87926ec1..7f70e2f2 100644 --- a/apps/site/docs/zh/faq.md +++ b/apps/site/docs/zh/faq.md @@ -16,11 +16,9 @@ Midscene 存在一些局限性,我们仍在努力改进。 2. 稳定性风险:即使是 GPT-4o 也无法确保 100% 返回正确答案。遵循 [编写提示词的技巧](./prompting-tips) 可以帮助提高 SDK 稳定性。 3. 元素访问受限:由于我们使用 JavaScript 从页面提取元素,所以无法访问 iframe 内部的元素。 -## 选用那个 LLM 模型? +## 能否选用 `gpt-4o` 以外的其他模型? -Midscene 需要一个能够理解用户界面的多模态大型语言模型。目前,我们发现 OpenAI 的 GPT-4o 表现最好,远超其它模型。 - -你可以根据需要[自定义模型和服务商](./model-provider.html)。 +可以。你可以[自定义模型和服务商](./model-provider.html)。 ## 关于 token 成本 diff --git a/apps/site/docs/zh/model-provider.md b/apps/site/docs/zh/model-provider.md index 444898b1..c70b7de1 100644 --- a/apps/site/docs/zh/model-provider.md +++ b/apps/site/docs/zh/model-provider.md @@ -17,9 +17,6 @@ export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz" # 可选, 如果你想更换 base URL export OPENAI_BASE_URL="https://..." -# 可选, 如果你想使用 Azure OpenAI 服务 -export OPENAI_USE_AZURE="true" - # 可选, 如果你想指定模型名称 export MIDSCENE_MODEL_NAME='qwen-vl-max-lates'; @@ -30,12 +27,36 @@ export MIDSCENE_OPENAI_INIT_CONFIG_JSON='{"baseURL":"....","defaultHeaders":{"ke export MIDSCENE_OPENAI_SOCKS_PROXY="socks5://127.0.0.1:1080" ``` -说明: +## 使用 Azure OpenAI 服务时的配置 + +```bash +export MIDSCENE_USE_AZURE_OPENAI=1 +export MIDSCENE_AZURE_OPENAI_SCOPE="https://cognitiveservices.azure.com/.default" +export MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON='{"apiVersion": "2024-11-01-preview", "endpoint": "...", "deployment": "..."}' +``` + +## 选用 `gpt-4o` 以外的其他模型 + +我们发现 `gpt-4o` 是目前表现最佳的模型。其他已知支持的模型有:`qwen-vl-max-latest` (千问), `gemini-1.5-pro`, `doubao-vision-pro-32k` (豆包) + +如果你想要使用其他模型,请遵循以下步骤: + +1. 选择一个支持视觉输入的模型(也就是“多模态模型”)。 +2. 找出如何使用 OpenAI SDK 兼容的方式调用它,模型提供商一般都会提供这样的接入点,你需要配置的是 `OPENAI_BASE_URL`, `OPENAI_API_KEY` 和 `MIDSCENE_MODEL_NAME`。 +3. 如果发现使用新模型后效果不佳,可以尝试使用一些简短且清晰的提示词(或回滚到之前的模型)。更多详情请参阅 [Prompting Tips](./prompting-tips.html)。 +4. 请遵守各模型的使用条款。 + +## 示例:使用 Google 的 `gemini-1.5-pro` 模型 + +配置环境变量: -- 务必选择一个支持视觉输入的模型。目前我们已知支持的模型有:`gpt-4o`, `qwen-vl-max-latest` (千问), `gemini-1.5-pro` -- 请遵守各项模型的使用条款 +```bash +export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai" +export OPENAI_API_KEY="....." +export MIDSCENE_MODEL_NAME="gemini-1.5-pro" +``` -## 示例:使用部署在阿里云的 `qwen-vl-max-latest` 模型 +## 示例:使用阿里云的 `qwen-vl-max-latest` 模型 配置环境变量: @@ -44,3 +65,15 @@ export OPENAI_API_KEY="sk-..." export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" export MIDSCENE_MODEL_NAME="qwen-vl-max-latest" ``` + +## 示例:使用火山云的豆包 `doubao-vision-pro-32k` 模型 + +调用前需要配置推理点:https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint + +配置环境变量: + +```bash +export OPENAI_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" +export OPENAI_API_KEY="..." +export MIDSCENE_MODEL_NAME="ep-202....." +``` diff --git a/packages/midscene/package.json b/packages/midscene/package.json index 11575189..fb1da5dc 100644 --- a/packages/midscene/package.json +++ b/packages/midscene/package.json @@ -37,7 +37,9 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@azure/identity": "4.5.0", "@midscene/shared": "workspace:*", + "dirty-json": "0.9.2", "openai": "4.57.1", "optional": "0.1.4", "socks-proxy-agent": "8.0.4" diff --git a/packages/midscene/src/ai-model/openai/index.ts b/packages/midscene/src/ai-model/openai/index.ts index 1c6d70e4..44bbfd5f 100644 --- a/packages/midscene/src/ai-model/openai/index.ts +++ b/packages/midscene/src/ai-model/openai/index.ts @@ -1,16 +1,24 @@ import assert from 'node:assert'; import { AIResponseFormat, type AIUsageInfo } from '@/types'; +import { + DefaultAzureCredential, + getBearerTokenProvider, +} from '@azure/identity'; import { ifInBrowser } from '@midscene/shared/utils'; +import dJSON from 'dirty-json'; import OpenAI, { AzureOpenAI } from 'openai'; import type { ChatCompletionMessageParam } from 'openai/resources'; import { SocksProxyAgent } from 'socks-proxy-agent'; import { + MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON, + MIDSCENE_AZURE_OPENAI_SCOPE, MIDSCENE_DANGEROUSLY_PRINT_ALL_CONFIG, MIDSCENE_DEBUG_AI_PROFILE, MIDSCENE_LANGSMITH_DEBUG, MIDSCENE_MODEL_NAME, MIDSCENE_OPENAI_INIT_CONFIG_JSON, MIDSCENE_OPENAI_SOCKS_PROXY, + MIDSCENE_USE_AZURE_OPENAI, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_USE_AZURE, @@ -26,6 +34,7 @@ import { assertSchema } from '../prompt/util'; export function preferOpenAIModel(preferVendor?: 'coze' | 'openAI') { if (preferVendor && preferVendor !== 'openAI') return false; if (getAIConfig(OPENAI_API_KEY)) return true; + if (getAIConfig(MIDSCENE_USE_AZURE_OPENAI)) return true; return Boolean(getAIConfig(MIDSCENE_OPENAI_INIT_CONFIG_JSON)); } @@ -47,7 +56,9 @@ async function createOpenAI() { const socksProxy = getAIConfig(MIDSCENE_OPENAI_SOCKS_PROXY); const socksAgent = socksProxy ? new SocksProxyAgent(socksProxy) : undefined; + if (getAIConfig(OPENAI_USE_AZURE)) { + // this is deprecated openai = new AzureOpenAI({ baseURL: getAIConfig(OPENAI_BASE_URL), apiKey: getAIConfig(OPENAI_API_KEY), @@ -55,6 +66,27 @@ async function createOpenAI() { ...extraConfig, dangerouslyAllowBrowser: true, }); + } else if (getAIConfig(MIDSCENE_USE_AZURE_OPENAI)) { + // sample code: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/openai/openai/samples/cookbook/simpleCompletionsPage/app.js + const scope = getAIConfig(MIDSCENE_AZURE_OPENAI_SCOPE); + + assert( + !ifInBrowser, + 'Azure OpenAI is not supported in browser with Midscene.', + ); + const credential = new DefaultAzureCredential(); + + assert(scope, 'MIDSCENE_AZURE_OPENAI_SCOPE is required'); + const tokenProvider = getBearerTokenProvider(credential, scope); + + const extraAzureConfig = getAIConfigInJson( + MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON, + ); + openai = new AzureOpenAI({ + azureADTokenProvider: tokenProvider, + ...extraConfig, + ...extraAzureConfig, + }); } else { openai = new OpenAI({ baseURL: getAIConfig(OPENAI_BASE_URL), @@ -157,12 +189,18 @@ export async function callToGetJSONObject( let jsonContent = safeJsonParse(response.content); if (jsonContent) return { content: jsonContent, usage: response.usage }; - jsonContent = extractJSONFromCodeBlock(response.content); + const cleanJsonString = extractJSONFromCodeBlock(response.content); try { - return { content: JSON.parse(jsonContent), usage: response.usage }; - } catch { - throw Error(`failed to parse json response: ${response.content}`); - } + jsonContent = JSON.parse(cleanJsonString); + } catch {} + if (jsonContent) return { content: jsonContent, usage: response.usage }; + + try { + jsonContent = dJSON.parse(cleanJsonString); + } catch {} + if (jsonContent) return { content: jsonContent, usage: response.usage }; + + throw Error(`failed to parse json response: ${response.content}`); } export function extractJSONFromCodeBlock(response: string) { diff --git a/packages/midscene/src/ai-model/openai/types.d.ts b/packages/midscene/src/ai-model/openai/types.d.ts new file mode 100644 index 00000000..a0e87e6c --- /dev/null +++ b/packages/midscene/src/ai-model/openai/types.d.ts @@ -0,0 +1 @@ +declare module 'dirty-json'; diff --git a/packages/midscene/src/ai-model/prompt/planning.ts b/packages/midscene/src/ai-model/prompt/planning.ts index 5fb90c8f..def1a97d 100644 --- a/packages/midscene/src/ai-model/prompt/planning.ts +++ b/packages/midscene/src/ai-model/prompt/planning.ts @@ -52,7 +52,7 @@ You are a versatile professional in software UI automation. Your outstanding con - All the actions you composed MUST be based on the page context information you get. - Trust the "What have been done" field about the task (if any), don't repeat actions in it. -- Respond only with valid JSON. Do not write an introduction or summary. +- Respond only with valid JSON. Do not write an introduction or summary or markdown prefix like \`\`\`json\`. - If you cannot plan any action at all (i.e. empty actions array), set reason in the \`error\` field. ## About the \`actions\` field @@ -140,7 +140,6 @@ By viewing the page screenshot and description, you should consider this and out * The "English" option button is not shown in the screenshot now, it means it may only show after the previous actions are finished. So the last action will have a \`null\` value in the \`locate\` field. * The task cannot be accomplished (because we cannot see the "English" option now), so a \`furtherPlan\` field is needed. -\`\`\`json { "actions":[ { @@ -171,8 +170,6 @@ By viewing the page screenshot and description, you should consider this and out "whatHaveDone": "Click the language switch button and wait 1s" } } -\`\`\` - ## Example #2 : Tolerate the error situation only when the instruction is an "if" statement @@ -181,7 +178,6 @@ If the user says "If there is a popup, close it", you should consider this and o * By viewing the page screenshot and description, you cannot find the popup, so the condition is falsy. * The instruction itself is an "if" statement, it means the user can tolerate this situation, so you should leave a \`FalsyConditionStatement\` action. -\`\`\`json { "actions": [{ "thought": "There is no popup on the page", @@ -192,18 +188,15 @@ If the user says "If there is a popup, close it", you should consider this and o "taskWillBeAccomplished": true, "furtherPlan": null } -\`\`\` For contrast, if the user says "Close the popup" in this situation, you should consider this and output the JSON: -\`\`\`json { "actions": [], "error": "The instruction and page context are irrelevant, there is no popup on the page", "taskWillBeAccomplished": true, "furtherPlan": null } -\`\`\` ## Example #3 : When task is accomplished, don't plan more actions @@ -224,6 +217,7 @@ When the user ask to "Wait 4s", you should consider this: ## Bad case #1 : Missing \`prompt\` in the 'Locate' field; Missing \`furtherPlan\` field when the task won't be accomplished Wrong output: + { "actions":[ { diff --git a/packages/midscene/src/env.ts b/packages/midscene/src/env.ts index e2a233c0..b906e218 100644 --- a/packages/midscene/src/env.ts +++ b/packages/midscene/src/env.ts @@ -11,11 +11,19 @@ export const MIDSCENE_OPENAI_SOCKS_PROXY = 'MIDSCENE_OPENAI_SOCKS_PROXY'; export const OPENAI_API_KEY = 'OPENAI_API_KEY'; export const OPENAI_BASE_URL = 'OPENAI_BASE_URL'; export const MIDSCENE_MODEL_TEXT_ONLY = 'MIDSCENE_MODEL_TEXT_ONLY'; -export const OPENAI_USE_AZURE = 'OPENAI_USE_AZURE'; + export const MIDSCENE_CACHE = 'MIDSCENE_CACHE'; export const MATCH_BY_POSITION = 'MATCH_BY_POSITION'; export const MIDSCENE_REPORT_TAG_NAME = 'MIDSCENE_REPORT_TAG_NAME'; +export const MIDSCENE_USE_AZURE_OPENAI = 'MIDSCENE_USE_AZURE_OPENAI'; +export const MIDSCENE_AZURE_OPENAI_SCOPE = 'MIDSCENE_AZURE_OPENAI_SCOPE'; +export const MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON = + 'MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON'; + +// @deprecated +export const OPENAI_USE_AZURE = 'OPENAI_USE_AZURE'; + const allConfigFromEnv = () => { return { [MIDSCENE_OPENAI_INIT_CONFIG_JSON]: @@ -39,6 +47,13 @@ const allConfigFromEnv = () => { process.env[MIDSCENE_REPORT_TAG_NAME] || undefined, [MIDSCENE_OPENAI_SOCKS_PROXY]: process.env[MIDSCENE_OPENAI_SOCKS_PROXY] || undefined, + [MIDSCENE_USE_AZURE_OPENAI]: + process.env[MIDSCENE_USE_AZURE_OPENAI] || undefined, + [MIDSCENE_AZURE_OPENAI_SCOPE]: + process.env[MIDSCENE_AZURE_OPENAI_SCOPE] || + 'https://cognitiveservices.azure.com/.default', + [MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON]: + process.env[MIDSCENE_AZURE_OPENAI_INIT_CONFIG_JSON] || undefined, }; }; diff --git a/packages/midscene/tests/ai/openai.test.ts b/packages/midscene/tests/ai/connectivity.test.ts similarity index 80% rename from packages/midscene/tests/ai/openai.test.ts rename to packages/midscene/tests/ai/connectivity.test.ts index 1370f74a..ec742ead 100644 --- a/packages/midscene/tests/ai/openai.test.ts +++ b/packages/midscene/tests/ai/connectivity.test.ts @@ -1,7 +1,15 @@ import { AIActionType } from '@/ai-model/common'; import { call, callToGetJSONObject } from '@/ai-model/openai'; +import { base64Encoded } from '@/image'; +import dotenv from 'dotenv'; +import { getFixture } from 'tests/utils'; import { describe, expect, it, vi } from 'vitest'; +const result = dotenv.config({ debug: true }); +if (result.error) { + throw result.error; +} + vi.setConfig({ testTimeout: 20 * 1000, }); @@ -35,10 +43,11 @@ describe('openai sdk connectivity', () => { ], AIActionType.EXTRACT_DATA, ); - expect(result.content.answer).toBe(15); + expect(result.content).toEqual({ answer: 15 }); }); it('image input', async () => { + const imagePath = getFixture('baidu.png'); const result = await call([ { role: 'user', @@ -50,7 +59,7 @@ describe('openai sdk connectivity', () => { { type: 'image_url', image_url: { - url: 'https://portal.volccdn.com/obj/volcfe/bee_prod/biz_950/tos_38e6e81e1366482ed046045e72b0684d.png', + url: base64Encoded(imagePath), detail: 'high', }, }, diff --git a/packages/midscene/tests/ai/evaluate/inspect.test.ts b/packages/midscene/tests/ai/evaluate/inspect.test.ts index 236940a5..049be393 100644 --- a/packages/midscene/tests/ai/evaluate/inspect.test.ts +++ b/packages/midscene/tests/ai/evaluate/inspect.test.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync } from 'node:fs'; +import { readFileSync } from 'node:fs'; import path from 'node:path'; import { describe } from 'node:test'; import { AiInspectElement, plan } from '@/ai-model'; @@ -13,6 +13,7 @@ import { repeat, runTestCases, } from './test-suite/util'; +import 'dotenv/config'; const repeatTime = 2; const relocateAfterPlanning = false; diff --git a/packages/midscene/tests/unit-test/utils.test.ts b/packages/midscene/tests/unit-test/utils.test.ts index 2a6c18be..90c98c83 100644 --- a/packages/midscene/tests/unit-test/utils.test.ts +++ b/packages/midscene/tests/unit-test/utils.test.ts @@ -98,6 +98,10 @@ describe('extractJSONFromCodeBlock', () => { const input = '```json\n{ "key": "value" }\n```'; const result = extractJSONFromCodeBlock(input); expect(result).toBe('{ "key": "value" }'); + + const input2 = ' ```JSON\n{ "key": "value" }\n```'; + const result2 = extractJSONFromCodeBlock(input2); + expect(result2).toBe('{ "key": "value" }'); }); it('should extract JSON from a code block without language specifier', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34772fbc..a05b512e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,9 +142,15 @@ importers: packages/midscene: dependencies: + '@azure/identity': + specifier: 4.5.0 + version: 4.5.0 '@midscene/shared': specifier: workspace:* version: link:../shared + dirty-json: + specifier: 0.9.2 + version: 0.9.2 openai: specifier: 4.57.1 version: 4.57.1(zod@3.23.8) @@ -459,6 +465,50 @@ packages: resolution: {integrity: sha512-qOqQG9o97Q4tIZXZyWI7JuDZGJi3yibTN7LiGLmnzNLaIhmpv26BWj5OYJibUyQLVH/aTjdZSNx4spa7EihUzg==} engines: {node: '>= 10'} + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.9.0': + resolution: {integrity: sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==} + engines: {node: '>=18.0.0'} + + '@azure/core-client@1.9.2': + resolution: {integrity: sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.18.1': + resolution: {integrity: sha512-/wS73UEDrxroUEVywEm7J0p2c+IIiVxyfigCGfsKvCxxCET4V/Hef2aURqltrXMRjNmdmt5IuOgIpl8f6xdO5A==} + engines: {node: '>=18.0.0'} + + '@azure/core-tracing@1.2.0': + resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} + engines: {node: '>=18.0.0'} + + '@azure/core-util@1.11.0': + resolution: {integrity: sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==} + engines: {node: '>=18.0.0'} + + '@azure/identity@4.5.0': + resolution: {integrity: sha512-EknvVmtBuSIic47xkOqyNabAme0RYTw52BTMz8eBgU1ysTyMrD1uOoM+JdS0J/4Yfp98IBT3osqq3BfwSaNaGQ==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.1.4': + resolution: {integrity: sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==} + engines: {node: '>=18.0.0'} + + '@azure/msal-browser@3.28.0': + resolution: {integrity: sha512-1c1qUF6vB52mWlyoMem4xR1gdwiQWYEQB2uhDkbAL4wVJr8WmAcXybc1Qs33y19N4BdPI8/DHI7rPE8L5jMtWw==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@14.16.0': + resolution: {integrity: sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@2.16.2': + resolution: {integrity: sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==} + engines: {node: '>=16'} + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -4150,6 +4200,9 @@ packages: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-equal@0.0.1: resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==} engines: {node: '>=0.4.0'} @@ -4892,6 +4945,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dirty-json@0.9.2: + resolution: {integrity: sha512-7SCDfnQtBObcngVXNPZcnxGxqqPTK4UqeXeKAch+RGH5qpqadWbV9FmN71x9Bb4tTs0TNFb4FT/4Kz4P4Cjqcw==} + engines: {node: '>=6.0.0'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -4966,6 +5023,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + edge-paths@3.0.5: resolution: {integrity: sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==} engines: {node: '>=14.0.0'} @@ -6343,9 +6403,25 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + kind-of@2.0.1: resolution: {integrity: sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==} engines: {node: '>=0.10.0'} @@ -6397,6 +6473,10 @@ packages: levdist@1.0.0: resolution: {integrity: sha512-YguwC2spb0pqpJM3a5OsBhih/GG2ZHoaSHnmBqhEI7997a36buhqcRTegEjozHxyxByIwLpZHZTVYMThq+Zd3g==} + lex@1.7.9: + resolution: {integrity: sha512-vzaalVBmFLnMaedq0QAsBAaXsWahzRpvnIBdBjj7y+7EKTS6lnziU2y/PsU2c6rV5qYj2B5IDw0uNJ9peXD0vw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -6471,12 +6551,27 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -6492,6 +6587,9 @@ packages: lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} @@ -8872,6 +8970,10 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} @@ -8907,6 +9009,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.fromcodepoint@0.2.1: + resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + string.prototype.trim@1.2.9: resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} engines: {node: '>= 0.4'} @@ -9349,6 +9454,9 @@ packages: resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} engines: {node: '>=18.17'} + unescape-js@1.1.4: + resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -9455,6 +9563,9 @@ packages: resolution: {integrity: sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==} engines: {node: '>= 0.8.0'} + utf8@3.0.0: + resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + utif2@4.1.0: resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} @@ -9468,6 +9579,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -9976,6 +10091,85 @@ snapshots: '@ast-grep/napi-win32-ia32-msvc': 0.16.0 '@ast-grep/napi-win32-x64-msvc': 0.16.0 + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + tslib: 2.8.1 + + '@azure/core-client@1.9.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.18.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-rest-pipeline@1.18.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.11.0': + dependencies: + '@azure/abort-controller': 2.1.2 + tslib: 2.8.1 + + '@azure/identity@4.5.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.18.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + '@azure/msal-browser': 3.28.0 + '@azure/msal-node': 2.16.2 + events: 3.3.0 + jws: 4.0.0 + open: 8.4.2 + stoppable: 1.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.1.4': + dependencies: + tslib: 2.8.1 + + '@azure/msal-browser@3.28.0': + dependencies: + '@azure/msal-common': 14.16.0 + + '@azure/msal-common@14.16.0': {} + + '@azure/msal-node@2.16.2': + dependencies: + '@azure/msal-common': 14.16.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -15022,6 +15216,8 @@ snapshots: buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} + buffer-equal@0.0.1: {} buffer-from@1.1.2: {} @@ -15842,6 +16038,12 @@ snapshots: dependencies: path-type: 4.0.0 + dirty-json@0.9.2: + dependencies: + lex: 1.7.9 + unescape-js: 1.1.4 + utf8: 3.0.0 + dlv@1.1.3: {} doctrine-temporary-fork@2.1.0: @@ -15961,6 +16163,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + edge-paths@3.0.5: dependencies: '@types/which': 2.0.2 @@ -17750,6 +17956,19 @@ snapshots: jsonparse@1.3.1: {} + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -17757,6 +17976,28 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwa@2.0.0: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + jws@4.0.0: + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + kind-of@2.0.1: dependencies: is-buffer: 1.1.6 @@ -17798,6 +18039,8 @@ snapshots: levdist@1.0.0: {} + lex@1.7.9: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -17880,10 +18123,20 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + lodash.isfunction@3.0.9: {} + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + lodash.isplainobject@4.0.6: {} + lodash.isstring@4.0.1: {} + lodash.kebabcase@4.1.1: {} lodash.map@4.6.0: {} @@ -17894,6 +18147,8 @@ snapshots: lodash.mergewith@4.6.2: {} + lodash.once@4.1.1: {} + lodash.snakecase@4.1.1: {} lodash.startcase@4.4.0: {} @@ -20781,6 +21036,8 @@ snapshots: std-env@3.7.0: {} + stoppable@1.1.0: {} + stream-browserify@3.0.0: dependencies: inherits: 2.0.4 @@ -20829,6 +21086,8 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string.fromcodepoint@0.2.1: {} + string.prototype.trim@1.2.9: dependencies: call-bind: 1.0.7 @@ -21305,6 +21564,10 @@ snapshots: undici@6.20.1: {} + unescape-js@1.1.4: + dependencies: + string.fromcodepoint: 0.2.1 + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -21418,6 +21681,8 @@ snapshots: userhome@1.0.1: {} + utf8@3.0.0: {} + utif2@4.1.0: dependencies: pako: 1.0.11 @@ -21434,6 +21699,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + uuid@9.0.1: {} uvu@0.5.6: