@midscene/core 1.17.5 → 1.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/es/agent/utils.mjs +1 -1
  2. package/dist/es/ai-model/models/jev/locate.mjs +2 -12
  3. package/dist/es/ai-model/models/jev/locate.mjs.map +1 -1
  4. package/dist/es/ai-model/service-caller/call-ai.mjs +4 -1
  5. package/dist/es/ai-model/service-caller/call-ai.mjs.map +1 -1
  6. package/dist/es/ai-model/service-caller/openai/siwc/authorization.mjs +4 -3
  7. package/dist/es/ai-model/service-caller/openai/siwc/authorization.mjs.map +1 -1
  8. package/dist/es/ai-model/service-caller/openai/siwc/network.mjs +36 -0
  9. package/dist/es/ai-model/service-caller/openai/siwc/network.mjs.map +1 -0
  10. package/dist/es/ai-model/service-caller/openai/siwc/refresh.mjs +5 -3
  11. package/dist/es/ai-model/service-caller/openai/siwc/refresh.mjs.map +1 -1
  12. package/dist/es/ai-model/service-caller/openai/siwc/token-manager.mjs +7 -4
  13. package/dist/es/ai-model/service-caller/openai/siwc/token-manager.mjs.map +1 -1
  14. package/dist/es/ai-model/shared/json.mjs +6 -2
  15. package/dist/es/ai-model/shared/json.mjs.map +1 -1
  16. package/dist/es/report-html-template.mjs +1 -1
  17. package/dist/es/siwc.mjs +4 -0
  18. package/dist/es/task-runner.mjs +7 -2
  19. package/dist/es/task-runner.mjs.map +1 -1
  20. package/dist/es/utils.mjs +1 -1
  21. package/dist/lib/agent/utils.js +1 -1
  22. package/dist/lib/ai-model/models/jev/locate.js +2 -12
  23. package/dist/lib/ai-model/models/jev/locate.js.map +1 -1
  24. package/dist/lib/ai-model/service-caller/call-ai.js +4 -1
  25. package/dist/lib/ai-model/service-caller/call-ai.js.map +1 -1
  26. package/dist/lib/ai-model/service-caller/openai/siwc/authorization.js +3 -2
  27. package/dist/lib/ai-model/service-caller/openai/siwc/authorization.js.map +1 -1
  28. package/dist/lib/ai-model/service-caller/openai/siwc/network.js +73 -0
  29. package/dist/lib/ai-model/service-caller/openai/siwc/network.js.map +1 -0
  30. package/dist/lib/ai-model/service-caller/openai/siwc/refresh.js +4 -2
  31. package/dist/lib/ai-model/service-caller/openai/siwc/refresh.js.map +1 -1
  32. package/dist/lib/ai-model/service-caller/openai/siwc/token-manager.js +7 -4
  33. package/dist/lib/ai-model/service-caller/openai/siwc/token-manager.js.map +1 -1
  34. package/dist/lib/ai-model/shared/json.js +6 -2
  35. package/dist/lib/ai-model/shared/json.js.map +1 -1
  36. package/dist/lib/report-html-template.js +1 -1
  37. package/dist/lib/siwc.js +52 -0
  38. package/dist/lib/siwc.js.map +1 -0
  39. package/dist/lib/task-runner.js +7 -2
  40. package/dist/lib/task-runner.js.map +1 -1
  41. package/dist/lib/utils.js +1 -1
  42. package/dist/types/ai-model/models/jev/locate.d.ts.map +1 -1
  43. package/dist/types/ai-model/service-caller/openai/siwc/authorization.d.ts.map +1 -1
  44. package/dist/types/ai-model/service-caller/openai/siwc/network.d.ts +8 -0
  45. package/dist/types/ai-model/service-caller/openai/siwc/network.d.ts.map +1 -0
  46. package/dist/types/ai-model/service-caller/openai/siwc/refresh.d.ts +1 -1
  47. package/dist/types/ai-model/service-caller/openai/siwc/refresh.d.ts.map +1 -1
  48. package/dist/types/ai-model/service-caller/openai/siwc/token-manager.d.ts +2 -1
  49. package/dist/types/ai-model/service-caller/openai/siwc/token-manager.d.ts.map +1 -1
  50. package/dist/types/ai-model/shared/json.d.ts +4 -15
  51. package/dist/types/ai-model/shared/json.d.ts.map +1 -1
  52. package/dist/types/siwc.d.ts +5 -0
  53. package/dist/types/siwc.d.ts.map +1 -0
  54. package/dist/types/task-runner.d.ts.map +1 -1
  55. package/package.json +10 -2
@@ -153,7 +153,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
153
153
  return;
154
154
  }
155
155
  }
156
- const getMidsceneVersion = ()=>"1.17.5";
156
+ const getMidsceneVersion = ()=>"1.17.6";
157
157
  const parsePrompt = (prompt)=>{
158
158
  if ('string' == typeof prompt) return {
159
159
  textPrompt: prompt,
@@ -17,10 +17,6 @@ const jevLocate = async ({ targetElementDescription, locateImage, options })=>{
17
17
  ...references.images
18
18
  },
19
19
  questions: {
20
- present: {
21
- type: 'noul',
22
- instructions: 'Is the target described in `target` present in `screenshot`? Reference images, if any, describe the target; they are not the screenshot.'
23
- },
24
20
  target: {
25
21
  type: 'box',
26
22
  instructions: 'The single target described in `target`, in `screenshot`.'
@@ -30,17 +26,11 @@ const jevLocate = async ({ targetElementDescription, locateImage, options })=>{
30
26
  abortSignal: options.abortSignal
31
27
  });
32
28
  const { answers } = systemOneResponseSchema.parse(JSON.parse(response.content));
33
- assert('noul' === answers.present.type && 'box' === answers.target.type, 'Invalid Jev locate answers');
29
+ assert(answers.target?.type === 'box', 'Invalid Jev locate answer');
34
30
  return {
35
31
  rawResponse: response.content,
36
32
  usage: response.usage,
37
- ...answers.present.noul >= 0.5 ? {
38
- locatedPixelResult: mapJevBox(answers.target.box, locateImage)
39
- } : {
40
- errors: [
41
- 'The target is not present in the screenshot'
42
- ]
43
- }
33
+ locatedPixelResult: mapJevBox(answers.target.box, locateImage)
44
34
  };
45
35
  };
46
36
  export { jevLocate };
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/models/jev/locate.mjs","sources":["../../../../../src/ai-model/models/jev/locate.ts"],"sourcesContent":["import { userPromptToString } from '@/common';\nimport { assert } from '@midscene/shared/utils';\nimport { callSystemOneAI } from '../../service-caller';\nimport { systemOneResponseSchema } from '../../service-caller/system-one/schema';\nimport type { LocateFn } from '../../workflows/grounding/types';\nimport { mapJevBox, prepareJevImage, prepareReferences } from './image';\n\nexport const jevLocate: LocateFn = async ({\n targetElementDescription,\n locateImage,\n options,\n}) => {\n const image = await prepareJevImage(locateImage.imageBase64);\n const references = await prepareReferences(targetElementDescription);\n const target = userPromptToString(targetElementDescription);\n const response = await callSystemOneAI(\n {\n state: { target, references: references.references },\n images: { screenshot: image.imageBase64, ...references.images },\n questions: {\n present: {\n type: 'noul',\n instructions:\n 'Is the target described in `target` present in `screenshot`? Reference images, if any, describe the target; they are not the screenshot.',\n },\n target: {\n type: 'box',\n instructions:\n 'The single target described in `target`, in `screenshot`.',\n },\n },\n },\n options.modelRuntime,\n { abortSignal: options.abortSignal },\n );\n const { answers } = systemOneResponseSchema.parse(\n JSON.parse(response.content),\n );\n assert(\n answers.present.type === 'noul' && answers.target.type === 'box',\n 'Invalid Jev locate answers',\n );\n return {\n rawResponse: response.content,\n usage: response.usage,\n ...(answers.present.noul >= 0.5\n ? {\n locatedPixelResult: mapJevBox(answers.target.box, locateImage),\n }\n : { errors: ['The target is not present in the screenshot'] }),\n };\n};\n"],"names":["jevLocate","targetElementDescription","locateImage","options","image","prepareJevImage","references","prepareReferences","target","userPromptToString","response","callSystemOneAI","answers","systemOneResponseSchema","JSON","assert","mapJevBox"],"mappings":";;;;;AAOO,MAAMA,YAAsB,OAAO,EACxCC,wBAAwB,EACxBC,WAAW,EACXC,OAAO,EACR;IACC,MAAMC,QAAQ,MAAMC,gBAAgBH,YAAY,WAAW;IAC3D,MAAMI,aAAa,MAAMC,kBAAkBN;IAC3C,MAAMO,SAASC,mBAAmBR;IAClC,MAAMS,WAAW,MAAMC,gBACrB;QACE,OAAO;YAAEH;YAAQ,YAAYF,WAAW,UAAU;QAAC;QACnD,QAAQ;YAAE,YAAYF,MAAM,WAAW;YAAE,GAAGE,WAAW,MAAM;QAAC;QAC9D,WAAW;YACT,SAAS;gBACP,MAAM;gBACN,cACE;YACJ;YACA,QAAQ;gBACN,MAAM;gBACN,cACE;YACJ;QACF;IACF,GACAH,QAAQ,YAAY,EACpB;QAAE,aAAaA,QAAQ,WAAW;IAAC;IAErC,MAAM,EAAES,OAAO,EAAE,GAAGC,wBAAwB,KAAK,CAC/CC,KAAK,KAAK,CAACJ,SAAS,OAAO;IAE7BK,OACEH,AAAyB,WAAzBA,QAAQ,OAAO,CAAC,IAAI,IAAeA,AAAwB,UAAxBA,QAAQ,MAAM,CAAC,IAAI,EACtD;IAEF,OAAO;QACL,aAAaF,SAAS,OAAO;QAC7B,OAAOA,SAAS,KAAK;QACrB,GAAIE,QAAQ,OAAO,CAAC,IAAI,IAAI,MACxB;YACE,oBAAoBI,UAAUJ,QAAQ,MAAM,CAAC,GAAG,EAAEV;QACpD,IACA;YAAE,QAAQ;gBAAC;aAA8C;QAAC,CAAC;IACjE;AACF"}
1
+ {"version":3,"file":"ai-model/models/jev/locate.mjs","sources":["../../../../../src/ai-model/models/jev/locate.ts"],"sourcesContent":["import { userPromptToString } from '@/common';\nimport { assert } from '@midscene/shared/utils';\nimport { callSystemOneAI } from '../../service-caller';\nimport { systemOneResponseSchema } from '../../service-caller/system-one/schema';\nimport type { LocateFn } from '../../workflows/grounding/types';\nimport { mapJevBox, prepareJevImage, prepareReferences } from './image';\n\nexport const jevLocate: LocateFn = async ({\n targetElementDescription,\n locateImage,\n options,\n}) => {\n const image = await prepareJevImage(locateImage.imageBase64);\n const references = await prepareReferences(targetElementDescription);\n const target = userPromptToString(targetElementDescription);\n const response = await callSystemOneAI(\n {\n state: { target, references: references.references },\n images: { screenshot: image.imageBase64, ...references.images },\n questions: {\n target: {\n type: 'box',\n instructions:\n 'The single target described in `target`, in `screenshot`.',\n },\n },\n },\n options.modelRuntime,\n { abortSignal: options.abortSignal },\n );\n const { answers } = systemOneResponseSchema.parse(\n JSON.parse(response.content),\n );\n assert(answers.target?.type === 'box', 'Invalid Jev locate answer');\n return {\n rawResponse: response.content,\n usage: response.usage,\n locatedPixelResult: mapJevBox(answers.target.box, locateImage),\n };\n};\n"],"names":["jevLocate","targetElementDescription","locateImage","options","image","prepareJevImage","references","prepareReferences","target","userPromptToString","response","callSystemOneAI","answers","systemOneResponseSchema","JSON","assert","mapJevBox"],"mappings":";;;;;AAOO,MAAMA,YAAsB,OAAO,EACxCC,wBAAwB,EACxBC,WAAW,EACXC,OAAO,EACR;IACC,MAAMC,QAAQ,MAAMC,gBAAgBH,YAAY,WAAW;IAC3D,MAAMI,aAAa,MAAMC,kBAAkBN;IAC3C,MAAMO,SAASC,mBAAmBR;IAClC,MAAMS,WAAW,MAAMC,gBACrB;QACE,OAAO;YAAEH;YAAQ,YAAYF,WAAW,UAAU;QAAC;QACnD,QAAQ;YAAE,YAAYF,MAAM,WAAW;YAAE,GAAGE,WAAW,MAAM;QAAC;QAC9D,WAAW;YACT,QAAQ;gBACN,MAAM;gBACN,cACE;YACJ;QACF;IACF,GACAH,QAAQ,YAAY,EACpB;QAAE,aAAaA,QAAQ,WAAW;IAAC;IAErC,MAAM,EAAES,OAAO,EAAE,GAAGC,wBAAwB,KAAK,CAC/CC,KAAK,KAAK,CAACJ,SAAS,OAAO;IAE7BK,OAAOH,QAAQ,MAAM,EAAE,SAAS,OAAO;IACvC,OAAO;QACL,aAAaF,SAAS,OAAO;QAC7B,OAAOA,SAAS,KAAK;QACrB,oBAAoBM,UAAUJ,QAAQ,MAAM,CAAC,GAAG,EAAEV;IACpD;AACF"}
@@ -128,7 +128,10 @@ async function callModelWithRetry(input) {
128
128
  if (error instanceof SiwcTokenExpiredError) {
129
129
  const refresh = async ()=>{
130
130
  assert(config.openaiApiKey, 'Missing rejected SIWC access token');
131
- await siwcTokenManager.refreshAccessToken(config.openaiApiKey);
131
+ await siwcTokenManager.refreshAccessToken(config.openaiApiKey, {
132
+ httpProxy: config.httpProxy,
133
+ socksProxy: config.socksProxy
134
+ });
132
135
  return prepareSiwcCall(input.modelRuntime, options);
133
136
  };
134
137
  const refreshed = options?.abortSignal ? await runWithAbortSignal(options.abortSignal, refresh) : await refresh();
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/service-caller/call-ai.mjs","sources":["../../../../src/ai-model/service-caller/call-ai.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { assert, uuid } from '@midscene/shared/utils';\nimport type { ModelRuntime } from '../models';\nimport { callCodex, prepareCodexCall } from './codex/call-codex';\nimport { isCodexAppServerProvider } from './codex/codex-app-server';\nimport {\n isModelCallRecordingEnabled,\n recordModelCallEvent,\n} from './model-call-recorder';\nimport { type PreparedOpenAIInput, callOpenAI } from './openai/call-openai';\nimport { prepareChatCompletion } from './openai/chat-completion/chat-completion';\nimport { prepareResponses } from './openai/responses/responses';\nimport {\n SiwcTokenExpiredError,\n prepareSiwcCall,\n siwcTokenManager,\n} from './openai/siwc';\nimport {\n buildRequestAbortSignal,\n resolveEffectiveTimeoutMs,\n runWithAbortSignal,\n waitForRetry,\n} from './request-timeout';\nimport {\n prepareSystemOne,\n callSystemOne as sendSystemOneRequest,\n} from './system-one';\nimport type { SystemOneRequest } from './system-one/schema';\nimport type { ModelCallMessages } from './types';\nimport type {\n AICallResult,\n CallAIOptions,\n ModelCallContext,\n ModelCallResult,\n} from './types';\nimport {\n appendAIRequestFailureSummary,\n buildUsageInfo,\n nextInternalCallId,\n normalizeRetryCount,\n toError,\n} from './utils';\n\nconst DEFAULT_MODEL_PROTOCOL = 'openai-chat';\n\nexport async function callGenerativeAI(\n messages: ModelCallMessages,\n originalModelRuntime: ModelRuntime,\n originalOptions?: CallAIOptions,\n): Promise<AICallResult> {\n originalOptions?.abortSignal?.throwIfAborted();\n const isStreaming = originalOptions?.stream === true;\n if (isStreaming) {\n // Explicit streaming is currently used only by recorder code generation.\n // Streaming can also be aggregated without onChunk, but then the caller\n // cannot consume incremental output, which is the purpose of this option.\n // Environment-enabled streaming supports that aggregation-only use case.\n // Once onChunk delivers a chunk, later errors are not retried to avoid\n // delivering duplicate content. Errors before the first chunk can retry.\n assert(\n typeof originalOptions?.onChunk === 'function',\n 'onChunk is required when stream: true is explicitly specified',\n );\n }\n\n // Resolve authentication constraints before protocol selection and validation.\n const { modelRuntime, options } = await prepareSiwcCall(\n originalModelRuntime,\n originalOptions,\n );\n\n const { config: modelConfig } = modelRuntime;\n return executeModelCall(modelRuntime, options, async () => {\n const input = { messages, modelRuntime, options };\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n return { kind: 'codex', input: prepareCodexCall(input) };\n }\n const protocol = modelConfig.protocol ?? DEFAULT_MODEL_PROTOCOL;\n\n assert(\n modelRuntime.adapter.supportedProtocols.includes(protocol),\n `Model adapter \"${modelConfig.modelFamily ?? 'default'}\" does not support protocol \"${protocol}\"`,\n );\n if (protocol === 'openai-responses') {\n return { kind: 'responses', input: await prepareResponses(input) };\n }\n return {\n kind: 'chat-completion',\n input: await prepareChatCompletion(input),\n };\n });\n}\n\nexport async function callSystemOneAI(\n request: SystemOneRequest,\n modelRuntime: ModelRuntime,\n options?: CallAIOptions,\n): Promise<AICallResult> {\n options?.abortSignal?.throwIfAborted();\n assert(!options?.stream, 'Jev does not support streaming output');\n assert(\n modelRuntime.config.modelFamily === 'jev',\n 'System One currently only supports the Jev model family in Midscene',\n );\n return executeModelCall(modelRuntime, options, async () => ({\n kind: 'system-one',\n input: await prepareSystemOne(modelRuntime.config, request),\n }));\n}\n\nasync function executeModelCall(\n modelRuntime: ModelRuntime,\n options: CallAIOptions | undefined,\n prepare: () => Promise<PreparedModelInput>,\n): Promise<AICallResult> {\n // Keep identifiers stable across all request attempts.\n const executionId = modelRuntime.executionId ?? `unscoped-${uuid()}`;\n const internalCallId = nextInternalCallId();\n const { config: modelConfig } = modelRuntime;\n\n const prepared = options?.abortSignal\n ? await runWithAbortSignal(options.abortSignal, prepare)\n : await prepare();\n\n const result = await callModelWithRetry({\n modelRuntime,\n options,\n executionId,\n internalCallId,\n prepared,\n });\n\n const {\n rawUsage,\n timeCost,\n totalTimeCost,\n retryCount,\n requestId,\n responseModelName,\n ...response\n } = result;\n\n const rawUsageInfo = buildUsageInfo({\n requestKind: prepared.kind,\n stream: response.isStreamed,\n usageData: rawUsage,\n timeCost,\n totalTimeCost,\n retryCount,\n requestId,\n responseModelName,\n modelName: modelConfig.modelName,\n modelDescription: modelConfig.modelDescription,\n slot: modelConfig.slot,\n internalCallId,\n });\n\n const usage = rawUsageInfo\n ? { ...rawUsageInfo, modelFallback: modelRuntime.modelFallback }\n : undefined;\n\n const debugProfileStats = getDebug('ai:profile:stats');\n const debugProfileDetail = getDebug('ai:profile:detail');\n debugProfileStats(\n `model, ${modelConfig.modelName}, mode, ${modelConfig.modelFamily || 'default'}, streaming, ${response.isStreamed}, prompt-tokens, ${usage?.prompt_tokens ?? ''}, completion-tokens, ${usage?.completion_tokens ?? ''}, total-tokens, ${usage?.total_tokens ?? ''}, cached-input, ${usage?.cached_input ?? ''}, cost-ms, ${timeCost ?? ''}, requestId, ${requestId ?? ''}, slot, ${modelConfig.slot}, configured-temperature, ${modelConfig.temperature ?? ''}`,\n );\n debugProfileDetail(`model usage detail: ${JSON.stringify(usage)}`);\n\n if (usage && modelRuntime.onUsage) {\n modelRuntime.onUsage(usage);\n }\n return { ...response, usage };\n}\n\ntype PreparedModelInput =\n | {\n kind: 'system-one';\n input: Awaited<ReturnType<typeof prepareSystemOne>>;\n }\n | { kind: 'codex'; input: ReturnType<typeof prepareCodexCall> }\n | PreparedOpenAIInput;\n\ntype ModelCallInput = {\n modelRuntime: ModelRuntime;\n options?: CallAIOptions;\n executionId: string;\n internalCallId: string;\n prepared: PreparedModelInput;\n};\n\n/** Carries the original failure across the retry boundary without replaying output. */\nclass NonRetryableModelCallError extends Error {\n readonly cause: Error;\n\n constructor(cause: Error) {\n super(cause.message, { cause });\n this.cause = cause;\n }\n}\n\nasync function callModelWithRetry(\n input: ModelCallInput,\n): Promise<ModelCallResult & { totalTimeCost: number; retryCount: number }> {\n const startTime = Date.now();\n const {\n modelRuntime: { config },\n options,\n } = input;\n const maxAttempts = normalizeRetryCount(config.retryCount) + 1;\n const retryInterval = config.retryInterval ?? 2000;\n const attemptErrors: Array<{ attempt: number; error: unknown }> = [];\n\n for (let attempt = 1; ; attempt++) {\n options?.abortSignal?.throwIfAborted();\n try {\n const result = await callModelOnce(input, attempt);\n return {\n ...result,\n totalTimeCost: Date.now() - startTime,\n retryCount: attempt - 1,\n };\n } catch (error) {\n options?.abortSignal?.throwIfAborted();\n if (error instanceof NonRetryableModelCallError) {\n throw error.cause;\n }\n // A rejected SIWC token gets one authentication retry, independently of\n // normal retryCount. Delivered chunks are rejected above before refreshing.\n if (error instanceof SiwcTokenExpiredError) {\n const refresh = async () => {\n assert(config.openaiApiKey, 'Missing rejected SIWC access token');\n await siwcTokenManager.refreshAccessToken(config.openaiApiKey);\n return prepareSiwcCall(input.modelRuntime, options);\n };\n // Cancel only this caller's wait; other callers may share the refresh.\n const refreshed = options?.abortSignal\n ? await runWithAbortSignal(options.abortSignal, refresh)\n : await refresh();\n options?.abortSignal?.throwIfAborted();\n try {\n const result = await callModelOnce(\n { ...input, ...refreshed },\n attempt + 1,\n );\n return {\n ...result,\n totalTimeCost: Date.now() - startTime,\n retryCount: attempt,\n };\n } catch (retryError) {\n // Do not re-enter either retry path after the refreshed request fails.\n throw retryError instanceof NonRetryableModelCallError\n ? retryError.cause\n : retryError;\n }\n }\n const lastError = toError(error);\n attemptErrors.push({ attempt, error: lastError });\n if (attempt === maxAttempts) {\n throw appendAIRequestFailureSummary(\n lastError,\n attemptErrors,\n maxAttempts,\n );\n }\n getDebug('ai:call', {\n console: true,\n warning: {\n kind: 'model-request-retry',\n modelName: config.modelName,\n error: lastError,\n callId: input.internalCallId,\n attempt,\n maxAttempts,\n delayMs: retryInterval,\n },\n })(\n `AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`,\n );\n }\n await waitForRetry(retryInterval, options?.abortSignal);\n }\n}\n\nasync function callModelOnce(\n {\n modelRuntime,\n options,\n executionId,\n internalCallId,\n prepared,\n }: ModelCallInput,\n attempt: number,\n): Promise<ModelCallResult> {\n const { config: modelConfig } = modelRuntime;\n const recordingEnabled = isModelCallRecordingEnabled();\n const recordEvent =\n recordingEnabled || modelRuntime.onRequest\n ? (event: Record<string, unknown>) => {\n if (event.type === 'request') {\n const request = event.request as { url?: string } | undefined;\n modelRuntime.onRequest?.(request?.url);\n }\n if (!recordingEnabled) return;\n void recordModelCallEvent({\n executionId,\n callId: internalCallId,\n semanticRetryAttempt: options?.semanticRetryAttempt,\n slot: modelConfig.slot,\n intent: modelConfig.intent,\n modelFamily: modelConfig.modelFamily,\n modelFallback: modelRuntime.modelFallback,\n ...event,\n attempt,\n });\n }\n : undefined;\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig.timeout);\n const { signal: requestSignal, cleanup } = buildRequestAbortSignal(\n effectiveTimeoutMs,\n options?.abortSignal,\n );\n let deliveredChunk = false;\n const onChunk = options?.onChunk;\n const context: ModelCallContext = {\n modelRuntime,\n executionId,\n recordEvent,\n requestSignal,\n effectiveTimeoutMs,\n options: onChunk\n ? {\n ...options,\n onChunk: (chunk) => {\n requestSignal.throwIfAborted();\n deliveredChunk = true;\n onChunk(chunk);\n },\n }\n : options,\n };\n try {\n return await runWithAbortSignal(requestSignal, () =>\n prepared.kind === 'system-one'\n ? sendSystemOneRequest(context, prepared.input)\n : prepared.kind === 'codex'\n ? callCodex(context, prepared.input)\n : callOpenAI(context, prepared),\n );\n } catch (error) {\n options?.abortSignal?.throwIfAborted();\n const lastError = toError(error);\n if (deliveredChunk) {\n throw new NonRetryableModelCallError(lastError);\n }\n throw lastError;\n } finally {\n cleanup();\n }\n}\n"],"names":["DEFAULT_MODEL_PROTOCOL","callGenerativeAI","messages","originalModelRuntime","originalOptions","isStreaming","assert","modelRuntime","options","prepareSiwcCall","modelConfig","executeModelCall","input","isCodexAppServerProvider","prepareCodexCall","protocol","prepareResponses","prepareChatCompletion","callSystemOneAI","request","prepareSystemOne","prepare","executionId","uuid","internalCallId","nextInternalCallId","prepared","runWithAbortSignal","result","callModelWithRetry","rawUsage","timeCost","totalTimeCost","retryCount","requestId","responseModelName","response","rawUsageInfo","buildUsageInfo","usage","undefined","debugProfileStats","getDebug","debugProfileDetail","JSON","NonRetryableModelCallError","Error","cause","startTime","Date","config","maxAttempts","normalizeRetryCount","retryInterval","attemptErrors","attempt","callModelOnce","error","SiwcTokenExpiredError","refresh","siwcTokenManager","refreshed","retryError","lastError","toError","appendAIRequestFailureSummary","waitForRetry","recordingEnabled","isModelCallRecordingEnabled","recordEvent","event","recordModelCallEvent","effectiveTimeoutMs","resolveEffectiveTimeoutMs","requestSignal","cleanup","buildRequestAbortSignal","deliveredChunk","onChunk","context","chunk","sendSystemOneRequest","callCodex","callOpenAI"],"mappings":"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"}
1
+ {"version":3,"file":"ai-model/service-caller/call-ai.mjs","sources":["../../../../src/ai-model/service-caller/call-ai.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { assert, uuid } from '@midscene/shared/utils';\nimport type { ModelRuntime } from '../models';\nimport { callCodex, prepareCodexCall } from './codex/call-codex';\nimport { isCodexAppServerProvider } from './codex/codex-app-server';\nimport {\n isModelCallRecordingEnabled,\n recordModelCallEvent,\n} from './model-call-recorder';\nimport { type PreparedOpenAIInput, callOpenAI } from './openai/call-openai';\nimport { prepareChatCompletion } from './openai/chat-completion/chat-completion';\nimport { prepareResponses } from './openai/responses/responses';\nimport {\n SiwcTokenExpiredError,\n prepareSiwcCall,\n siwcTokenManager,\n} from './openai/siwc';\nimport {\n buildRequestAbortSignal,\n resolveEffectiveTimeoutMs,\n runWithAbortSignal,\n waitForRetry,\n} from './request-timeout';\nimport {\n prepareSystemOne,\n callSystemOne as sendSystemOneRequest,\n} from './system-one';\nimport type { SystemOneRequest } from './system-one/schema';\nimport type { ModelCallMessages } from './types';\nimport type {\n AICallResult,\n CallAIOptions,\n ModelCallContext,\n ModelCallResult,\n} from './types';\nimport {\n appendAIRequestFailureSummary,\n buildUsageInfo,\n nextInternalCallId,\n normalizeRetryCount,\n toError,\n} from './utils';\n\nconst DEFAULT_MODEL_PROTOCOL = 'openai-chat';\n\nexport async function callGenerativeAI(\n messages: ModelCallMessages,\n originalModelRuntime: ModelRuntime,\n originalOptions?: CallAIOptions,\n): Promise<AICallResult> {\n originalOptions?.abortSignal?.throwIfAborted();\n const isStreaming = originalOptions?.stream === true;\n if (isStreaming) {\n // Explicit streaming is currently used only by recorder code generation.\n // Streaming can also be aggregated without onChunk, but then the caller\n // cannot consume incremental output, which is the purpose of this option.\n // Environment-enabled streaming supports that aggregation-only use case.\n // Once onChunk delivers a chunk, later errors are not retried to avoid\n // delivering duplicate content. Errors before the first chunk can retry.\n assert(\n typeof originalOptions?.onChunk === 'function',\n 'onChunk is required when stream: true is explicitly specified',\n );\n }\n\n // Resolve authentication constraints before protocol selection and validation.\n const { modelRuntime, options } = await prepareSiwcCall(\n originalModelRuntime,\n originalOptions,\n );\n\n const { config: modelConfig } = modelRuntime;\n return executeModelCall(modelRuntime, options, async () => {\n const input = { messages, modelRuntime, options };\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n return { kind: 'codex', input: prepareCodexCall(input) };\n }\n const protocol = modelConfig.protocol ?? DEFAULT_MODEL_PROTOCOL;\n\n assert(\n modelRuntime.adapter.supportedProtocols.includes(protocol),\n `Model adapter \"${modelConfig.modelFamily ?? 'default'}\" does not support protocol \"${protocol}\"`,\n );\n if (protocol === 'openai-responses') {\n return { kind: 'responses', input: await prepareResponses(input) };\n }\n return {\n kind: 'chat-completion',\n input: await prepareChatCompletion(input),\n };\n });\n}\n\nexport async function callSystemOneAI(\n request: SystemOneRequest,\n modelRuntime: ModelRuntime,\n options?: CallAIOptions,\n): Promise<AICallResult> {\n options?.abortSignal?.throwIfAborted();\n assert(!options?.stream, 'Jev does not support streaming output');\n assert(\n modelRuntime.config.modelFamily === 'jev',\n 'System One currently only supports the Jev model family in Midscene',\n );\n return executeModelCall(modelRuntime, options, async () => ({\n kind: 'system-one',\n input: await prepareSystemOne(modelRuntime.config, request),\n }));\n}\n\nasync function executeModelCall(\n modelRuntime: ModelRuntime,\n options: CallAIOptions | undefined,\n prepare: () => Promise<PreparedModelInput>,\n): Promise<AICallResult> {\n // Keep identifiers stable across all request attempts.\n const executionId = modelRuntime.executionId ?? `unscoped-${uuid()}`;\n const internalCallId = nextInternalCallId();\n const { config: modelConfig } = modelRuntime;\n\n const prepared = options?.abortSignal\n ? await runWithAbortSignal(options.abortSignal, prepare)\n : await prepare();\n\n const result = await callModelWithRetry({\n modelRuntime,\n options,\n executionId,\n internalCallId,\n prepared,\n });\n\n const {\n rawUsage,\n timeCost,\n totalTimeCost,\n retryCount,\n requestId,\n responseModelName,\n ...response\n } = result;\n\n const rawUsageInfo = buildUsageInfo({\n requestKind: prepared.kind,\n stream: response.isStreamed,\n usageData: rawUsage,\n timeCost,\n totalTimeCost,\n retryCount,\n requestId,\n responseModelName,\n modelName: modelConfig.modelName,\n modelDescription: modelConfig.modelDescription,\n slot: modelConfig.slot,\n internalCallId,\n });\n\n const usage = rawUsageInfo\n ? { ...rawUsageInfo, modelFallback: modelRuntime.modelFallback }\n : undefined;\n\n const debugProfileStats = getDebug('ai:profile:stats');\n const debugProfileDetail = getDebug('ai:profile:detail');\n debugProfileStats(\n `model, ${modelConfig.modelName}, mode, ${modelConfig.modelFamily || 'default'}, streaming, ${response.isStreamed}, prompt-tokens, ${usage?.prompt_tokens ?? ''}, completion-tokens, ${usage?.completion_tokens ?? ''}, total-tokens, ${usage?.total_tokens ?? ''}, cached-input, ${usage?.cached_input ?? ''}, cost-ms, ${timeCost ?? ''}, requestId, ${requestId ?? ''}, slot, ${modelConfig.slot}, configured-temperature, ${modelConfig.temperature ?? ''}`,\n );\n debugProfileDetail(`model usage detail: ${JSON.stringify(usage)}`);\n\n if (usage && modelRuntime.onUsage) {\n modelRuntime.onUsage(usage);\n }\n return { ...response, usage };\n}\n\ntype PreparedModelInput =\n | {\n kind: 'system-one';\n input: Awaited<ReturnType<typeof prepareSystemOne>>;\n }\n | { kind: 'codex'; input: ReturnType<typeof prepareCodexCall> }\n | PreparedOpenAIInput;\n\ntype ModelCallInput = {\n modelRuntime: ModelRuntime;\n options?: CallAIOptions;\n executionId: string;\n internalCallId: string;\n prepared: PreparedModelInput;\n};\n\n/** Carries the original failure across the retry boundary without replaying output. */\nclass NonRetryableModelCallError extends Error {\n readonly cause: Error;\n\n constructor(cause: Error) {\n super(cause.message, { cause });\n this.cause = cause;\n }\n}\n\nasync function callModelWithRetry(\n input: ModelCallInput,\n): Promise<ModelCallResult & { totalTimeCost: number; retryCount: number }> {\n const startTime = Date.now();\n const {\n modelRuntime: { config },\n options,\n } = input;\n const maxAttempts = normalizeRetryCount(config.retryCount) + 1;\n const retryInterval = config.retryInterval ?? 2000;\n const attemptErrors: Array<{ attempt: number; error: unknown }> = [];\n\n for (let attempt = 1; ; attempt++) {\n options?.abortSignal?.throwIfAborted();\n try {\n const result = await callModelOnce(input, attempt);\n return {\n ...result,\n totalTimeCost: Date.now() - startTime,\n retryCount: attempt - 1,\n };\n } catch (error) {\n options?.abortSignal?.throwIfAborted();\n if (error instanceof NonRetryableModelCallError) {\n throw error.cause;\n }\n // A rejected SIWC token gets one authentication retry, independently of\n // normal retryCount. Delivered chunks are rejected above before refreshing.\n if (error instanceof SiwcTokenExpiredError) {\n const refresh = async () => {\n assert(config.openaiApiKey, 'Missing rejected SIWC access token');\n await siwcTokenManager.refreshAccessToken(config.openaiApiKey, {\n httpProxy: config.httpProxy,\n socksProxy: config.socksProxy,\n });\n return prepareSiwcCall(input.modelRuntime, options);\n };\n // Cancel only this caller's wait; other callers may share the refresh.\n const refreshed = options?.abortSignal\n ? await runWithAbortSignal(options.abortSignal, refresh)\n : await refresh();\n options?.abortSignal?.throwIfAborted();\n try {\n const result = await callModelOnce(\n { ...input, ...refreshed },\n attempt + 1,\n );\n return {\n ...result,\n totalTimeCost: Date.now() - startTime,\n retryCount: attempt,\n };\n } catch (retryError) {\n // Do not re-enter either retry path after the refreshed request fails.\n throw retryError instanceof NonRetryableModelCallError\n ? retryError.cause\n : retryError;\n }\n }\n const lastError = toError(error);\n attemptErrors.push({ attempt, error: lastError });\n if (attempt === maxAttempts) {\n throw appendAIRequestFailureSummary(\n lastError,\n attemptErrors,\n maxAttempts,\n );\n }\n getDebug('ai:call', {\n console: true,\n warning: {\n kind: 'model-request-retry',\n modelName: config.modelName,\n error: lastError,\n callId: input.internalCallId,\n attempt,\n maxAttempts,\n delayMs: retryInterval,\n },\n })(\n `AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`,\n );\n }\n await waitForRetry(retryInterval, options?.abortSignal);\n }\n}\n\nasync function callModelOnce(\n {\n modelRuntime,\n options,\n executionId,\n internalCallId,\n prepared,\n }: ModelCallInput,\n attempt: number,\n): Promise<ModelCallResult> {\n const { config: modelConfig } = modelRuntime;\n const recordingEnabled = isModelCallRecordingEnabled();\n const recordEvent =\n recordingEnabled || modelRuntime.onRequest\n ? (event: Record<string, unknown>) => {\n if (event.type === 'request') {\n const request = event.request as { url?: string } | undefined;\n modelRuntime.onRequest?.(request?.url);\n }\n if (!recordingEnabled) return;\n void recordModelCallEvent({\n executionId,\n callId: internalCallId,\n semanticRetryAttempt: options?.semanticRetryAttempt,\n slot: modelConfig.slot,\n intent: modelConfig.intent,\n modelFamily: modelConfig.modelFamily,\n modelFallback: modelRuntime.modelFallback,\n ...event,\n attempt,\n });\n }\n : undefined;\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig.timeout);\n const { signal: requestSignal, cleanup } = buildRequestAbortSignal(\n effectiveTimeoutMs,\n options?.abortSignal,\n );\n let deliveredChunk = false;\n const onChunk = options?.onChunk;\n const context: ModelCallContext = {\n modelRuntime,\n executionId,\n recordEvent,\n requestSignal,\n effectiveTimeoutMs,\n options: onChunk\n ? {\n ...options,\n onChunk: (chunk) => {\n requestSignal.throwIfAborted();\n deliveredChunk = true;\n onChunk(chunk);\n },\n }\n : options,\n };\n try {\n return await runWithAbortSignal(requestSignal, () =>\n prepared.kind === 'system-one'\n ? sendSystemOneRequest(context, prepared.input)\n : prepared.kind === 'codex'\n ? callCodex(context, prepared.input)\n : callOpenAI(context, prepared),\n );\n } catch (error) {\n options?.abortSignal?.throwIfAborted();\n const lastError = toError(error);\n if (deliveredChunk) {\n throw new NonRetryableModelCallError(lastError);\n }\n throw lastError;\n } finally {\n cleanup();\n }\n}\n"],"names":["DEFAULT_MODEL_PROTOCOL","callGenerativeAI","messages","originalModelRuntime","originalOptions","isStreaming","assert","modelRuntime","options","prepareSiwcCall","modelConfig","executeModelCall","input","isCodexAppServerProvider","prepareCodexCall","protocol","prepareResponses","prepareChatCompletion","callSystemOneAI","request","prepareSystemOne","prepare","executionId","uuid","internalCallId","nextInternalCallId","prepared","runWithAbortSignal","result","callModelWithRetry","rawUsage","timeCost","totalTimeCost","retryCount","requestId","responseModelName","response","rawUsageInfo","buildUsageInfo","usage","undefined","debugProfileStats","getDebug","debugProfileDetail","JSON","NonRetryableModelCallError","Error","cause","startTime","Date","config","maxAttempts","normalizeRetryCount","retryInterval","attemptErrors","attempt","callModelOnce","error","SiwcTokenExpiredError","refresh","siwcTokenManager","refreshed","retryError","lastError","toError","appendAIRequestFailureSummary","waitForRetry","recordingEnabled","isModelCallRecordingEnabled","recordEvent","event","recordModelCallEvent","effectiveTimeoutMs","resolveEffectiveTimeoutMs","requestSignal","cleanup","buildRequestAbortSignal","deliveredChunk","onChunk","context","chunk","sendSystemOneRequest","callCodex","callOpenAI"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { createRemoteJWKSet, jwtVerify } from "jose";
1
+ import { jwtVerify } from "jose";
2
2
  import { tokenResponseError, withSiwcStage } from "./errors.mjs";
3
+ import { createSiwcJWKSet, fetchSiwc } from "./network.mjs";
3
4
  const ISSUER = 'https://auth.openai.com';
4
5
  const RESOURCE = 'https://api.openai.com/v1';
5
6
  function base64url(bytes) {
@@ -58,7 +59,7 @@ function parseSiwcCallback(url, attempt) {
58
59
  async function exchangeSiwcCode(attempt, callback, signal, dependencies) {
59
60
  signal.throwIfAborted();
60
61
  const response = await withSiwcStage('Token exchange', async ()=>{
61
- const response = await (dependencies?.fetch ?? fetch)(`${ISSUER}/api/accounts/oauth/token`, {
62
+ const response = await (dependencies?.fetch ?? fetchSiwc)(`${ISSUER}/api/accounts/oauth/token`, {
62
63
  method: 'POST',
63
64
  headers: {
64
65
  'Content-Type': 'application/x-www-form-urlencoded'
@@ -81,7 +82,7 @@ async function exchangeSiwcCode(attempt, callback, signal, dependencies) {
81
82
  if (!tokens || 'string' != typeof tokens.access_token || !tokens.access_token || 'string' != typeof tokens.refresh_token || !tokens.refresh_token || 'string' != typeof tokens.id_token || 'string' != typeof tokens.scope || 'number' != typeof tokens.expires_in || !Number.isFinite(tokens.expires_in) || tokens.expires_in <= 0 || 'string' != typeof tokens.token_type || 'bearer' !== tokens.token_type.toLowerCase()) throw new Error('OpenAI returned an invalid token response.');
82
83
  const scopes = tokens.scope.split(/\s+/);
83
84
  if (!scopes.includes('chatgpt.tokens.use.direct')) throw new Error('ChatGPT plan usage was not authorized.');
84
- const { payload } = await withSiwcStage('ID token verification', ()=>jwtVerify(tokens.id_token, dependencies?.keys ?? createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`)), {
85
+ const { payload } = await withSiwcStage('ID token verification', ()=>jwtVerify(tokens.id_token, dependencies?.keys ?? createSiwcJWKSet(ISSUER, signal), {
85
86
  issuer: ISSUER,
86
87
  audience: callback.clientId,
87
88
  algorithms: [
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/service-caller/openai/siwc/authorization.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/authorization.ts"],"sourcesContent":["import { type JWTVerifyGetKey, createRemoteJWKSet, jwtVerify } from 'jose';\nimport { tokenResponseError, withSiwcStage } from './errors';\n\nconst ISSUER = 'https://auth.openai.com';\nconst RESOURCE = 'https://api.openai.com/v1';\nexport interface SiwcLoginAttempt {\n hostId: string;\n redirectUri: string;\n clientId?: string;\n state: string;\n nonce: string;\n verifier: string;\n expiresAt: number;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\nexport async function createSiwcAuthorization(options: {\n hostId: string;\n clientId?: string;\n redirectUri: string;\n agentName: string;\n}) {\n const { hostId, clientId, redirectUri, agentName } = options;\n const random = () => base64url(crypto.getRandomValues(new Uint8Array(32)));\n const attempt: SiwcLoginAttempt = {\n hostId,\n redirectUri,\n clientId,\n state: random(),\n nonce: random(),\n verifier: random(),\n expiresAt: Date.now() + 5 * 60_000,\n };\n const challenge = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(attempt.verifier),\n );\n const url = new URL(`${ISSUER}/api/accounts/authorize`);\n url.search = new URLSearchParams({\n client_id: clientId ?? 'dynamic_agent_client',\n ...(clientId ? {} : { agent_name_hint: agentName }),\n ext_agent_host_id: hostId,\n response_type: 'code',\n redirect_uri: attempt.redirectUri,\n scope:\n 'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct',\n resource: RESOURCE,\n state: attempt.state,\n nonce: attempt.nonce,\n code_challenge_method: 'S256',\n code_challenge: base64url(new Uint8Array(challenge)),\n }).toString();\n return { attempt, url: url.toString() };\n}\n\nexport function parseSiwcCallback(url: string, attempt: SiwcLoginAttempt) {\n const callback = new URL(url);\n const expected = new URL(attempt.redirectUri);\n if (\n callback.origin !== expected.origin ||\n callback.pathname !== expected.pathname\n ) {\n return undefined;\n }\n const params = callback.searchParams;\n if (\n params.getAll('state').length !== 1 ||\n params.get('state') !== attempt.state\n ) {\n return undefined;\n }\n if (Date.now() >= attempt.expiresAt) {\n throw new Error('Authorization timed out. Start authorization again.');\n }\n if (params.has('error')) {\n throw new Error(\n `OpenAI authorization failed: ${params.get('error_description') || params.get('error')}`,\n );\n }\n const code = params.get('code');\n const clientId = params.get('client_id') || attempt.clientId;\n if (\n !code ||\n !clientId ||\n clientId === 'dynamic_agent_client' ||\n params.getAll('code').length !== 1 ||\n params.getAll('client_id').length > 1 ||\n (attempt.clientId && clientId !== attempt.clientId)\n ) {\n throw new Error('Invalid OpenAI authorization callback.');\n }\n return { code, clientId };\n}\n\nexport async function exchangeSiwcCode(\n attempt: SiwcLoginAttempt,\n callback: { code: string; clientId: string },\n signal: AbortSignal,\n dependencies?: { fetch: typeof fetch; keys: JWTVerifyGetKey },\n) {\n signal.throwIfAborted();\n const response = await withSiwcStage('Token exchange', async () => {\n const response = await (dependencies?.fetch ?? fetch)(\n `${ISSUER}/api/accounts/oauth/token`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: callback.clientId,\n code: callback.code,\n code_verifier: attempt.verifier,\n redirect_uri: attempt.redirectUri,\n resource: RESOURCE,\n }),\n signal,\n redirect: 'error',\n },\n );\n if (!response.ok) {\n throw await tokenResponseError(response);\n }\n return response;\n });\n const tokens = await withSiwcStage('Token response parsing', () =>\n response.json(),\n );\n if (\n !tokens ||\n typeof tokens.access_token !== 'string' ||\n !tokens.access_token ||\n typeof tokens.refresh_token !== 'string' ||\n !tokens.refresh_token ||\n typeof tokens.id_token !== 'string' ||\n typeof tokens.scope !== 'string' ||\n typeof tokens.expires_in !== 'number' ||\n !Number.isFinite(tokens.expires_in) ||\n tokens.expires_in <= 0 ||\n typeof tokens.token_type !== 'string' ||\n tokens.token_type.toLowerCase() !== 'bearer'\n ) {\n throw new Error('OpenAI returned an invalid token response.');\n }\n const scopes = tokens.scope.split(/\\s+/);\n if (!scopes.includes('chatgpt.tokens.use.direct')) {\n throw new Error('ChatGPT plan usage was not authorized.');\n }\n const { payload } = await withSiwcStage('ID token verification', () =>\n jwtVerify(\n tokens.id_token,\n dependencies?.keys ??\n createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`)),\n {\n issuer: ISSUER,\n audience: callback.clientId,\n algorithms: ['RS256'],\n // Some CI machines have incorrect system clocks. Do not compare ID token\n // exp/nbf claims against local time when accepting newly returned tokens.\n // Keep signature, issuer, audience and required-claim validation.\n clockTolerance: Number.POSITIVE_INFINITY,\n requiredClaims: ['sub', 'exp', 'iat', 'nonce'],\n },\n ),\n );\n if (payload.nonce !== attempt.nonce || !payload.sub) {\n throw new Error('OpenAI login identity or nonce did not match.');\n }\n signal.throwIfAborted();\n return {\n ...(typeof payload.email === 'string' ? { email: payload.email } : {}),\n ext_agent_host_id: attempt.hostId,\n client_id: callback.clientId,\n subject: payload.sub,\n issuer: ISSUER,\n access_token: tokens.access_token as string,\n refresh_token: tokens.refresh_token as string,\n id_token: tokens.id_token as string,\n scopes,\n expires_at: Date.now() + tokens.expires_in * 1000,\n };\n}\n"],"names":["ISSUER","RESOURCE","base64url","bytes","btoa","String","createSiwcAuthorization","options","hostId","clientId","redirectUri","agentName","random","crypto","Uint8Array","attempt","Date","challenge","TextEncoder","url","URL","URLSearchParams","parseSiwcCallback","callback","expected","params","Error","code","exchangeSiwcCode","signal","dependencies","response","withSiwcStage","fetch","tokenResponseError","tokens","Number","scopes","payload","jwtVerify","createRemoteJWKSet"],"mappings":"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"}
1
+ {"version":3,"file":"ai-model/service-caller/openai/siwc/authorization.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/authorization.ts"],"sourcesContent":["import { type JWTVerifyGetKey, jwtVerify } from 'jose';\nimport { tokenResponseError, withSiwcStage } from './errors';\nimport { createSiwcJWKSet, fetchSiwc } from './network';\n\nconst ISSUER = 'https://auth.openai.com';\nconst RESOURCE = 'https://api.openai.com/v1';\nexport interface SiwcLoginAttempt {\n hostId: string;\n redirectUri: string;\n clientId?: string;\n state: string;\n nonce: string;\n verifier: string;\n expiresAt: number;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\nexport async function createSiwcAuthorization(options: {\n hostId: string;\n clientId?: string;\n redirectUri: string;\n agentName: string;\n}) {\n const { hostId, clientId, redirectUri, agentName } = options;\n const random = () => base64url(crypto.getRandomValues(new Uint8Array(32)));\n const attempt: SiwcLoginAttempt = {\n hostId,\n redirectUri,\n clientId,\n state: random(),\n nonce: random(),\n verifier: random(),\n expiresAt: Date.now() + 5 * 60_000,\n };\n const challenge = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(attempt.verifier),\n );\n const url = new URL(`${ISSUER}/api/accounts/authorize`);\n url.search = new URLSearchParams({\n client_id: clientId ?? 'dynamic_agent_client',\n ...(clientId ? {} : { agent_name_hint: agentName }),\n ext_agent_host_id: hostId,\n response_type: 'code',\n redirect_uri: attempt.redirectUri,\n scope:\n 'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct',\n resource: RESOURCE,\n state: attempt.state,\n nonce: attempt.nonce,\n code_challenge_method: 'S256',\n code_challenge: base64url(new Uint8Array(challenge)),\n }).toString();\n return { attempt, url: url.toString() };\n}\n\nexport function parseSiwcCallback(url: string, attempt: SiwcLoginAttempt) {\n const callback = new URL(url);\n const expected = new URL(attempt.redirectUri);\n if (\n callback.origin !== expected.origin ||\n callback.pathname !== expected.pathname\n ) {\n return undefined;\n }\n const params = callback.searchParams;\n if (\n params.getAll('state').length !== 1 ||\n params.get('state') !== attempt.state\n ) {\n return undefined;\n }\n if (Date.now() >= attempt.expiresAt) {\n throw new Error('Authorization timed out. Start authorization again.');\n }\n if (params.has('error')) {\n throw new Error(\n `OpenAI authorization failed: ${params.get('error_description') || params.get('error')}`,\n );\n }\n const code = params.get('code');\n const clientId = params.get('client_id') || attempt.clientId;\n if (\n !code ||\n !clientId ||\n clientId === 'dynamic_agent_client' ||\n params.getAll('code').length !== 1 ||\n params.getAll('client_id').length > 1 ||\n (attempt.clientId && clientId !== attempt.clientId)\n ) {\n throw new Error('Invalid OpenAI authorization callback.');\n }\n return { code, clientId };\n}\n\nexport async function exchangeSiwcCode(\n attempt: SiwcLoginAttempt,\n callback: { code: string; clientId: string },\n signal: AbortSignal,\n dependencies?: { fetch: typeof fetch; keys: JWTVerifyGetKey },\n) {\n signal.throwIfAborted();\n const response = await withSiwcStage('Token exchange', async () => {\n const response = await (dependencies?.fetch ?? fetchSiwc)(\n `${ISSUER}/api/accounts/oauth/token`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: callback.clientId,\n code: callback.code,\n code_verifier: attempt.verifier,\n redirect_uri: attempt.redirectUri,\n resource: RESOURCE,\n }),\n signal,\n redirect: 'error',\n },\n );\n if (!response.ok) {\n throw await tokenResponseError(response);\n }\n return response;\n });\n const tokens = await withSiwcStage('Token response parsing', () =>\n response.json(),\n );\n if (\n !tokens ||\n typeof tokens.access_token !== 'string' ||\n !tokens.access_token ||\n typeof tokens.refresh_token !== 'string' ||\n !tokens.refresh_token ||\n typeof tokens.id_token !== 'string' ||\n typeof tokens.scope !== 'string' ||\n typeof tokens.expires_in !== 'number' ||\n !Number.isFinite(tokens.expires_in) ||\n tokens.expires_in <= 0 ||\n typeof tokens.token_type !== 'string' ||\n tokens.token_type.toLowerCase() !== 'bearer'\n ) {\n throw new Error('OpenAI returned an invalid token response.');\n }\n const scopes = tokens.scope.split(/\\s+/);\n if (!scopes.includes('chatgpt.tokens.use.direct')) {\n throw new Error('ChatGPT plan usage was not authorized.');\n }\n const { payload } = await withSiwcStage('ID token verification', () =>\n jwtVerify(\n tokens.id_token,\n dependencies?.keys ?? createSiwcJWKSet(ISSUER, signal),\n {\n issuer: ISSUER,\n audience: callback.clientId,\n algorithms: ['RS256'],\n // Some CI machines have incorrect system clocks. Do not compare ID token\n // exp/nbf claims against local time when accepting newly returned tokens.\n // Keep signature, issuer, audience and required-claim validation.\n clockTolerance: Number.POSITIVE_INFINITY,\n requiredClaims: ['sub', 'exp', 'iat', 'nonce'],\n },\n ),\n );\n if (payload.nonce !== attempt.nonce || !payload.sub) {\n throw new Error('OpenAI login identity or nonce did not match.');\n }\n signal.throwIfAborted();\n return {\n ...(typeof payload.email === 'string' ? { email: payload.email } : {}),\n ext_agent_host_id: attempt.hostId,\n client_id: callback.clientId,\n subject: payload.sub,\n issuer: ISSUER,\n access_token: tokens.access_token as string,\n refresh_token: tokens.refresh_token as string,\n id_token: tokens.id_token as string,\n scopes,\n expires_at: Date.now() + tokens.expires_in * 1000,\n };\n}\n"],"names":["ISSUER","RESOURCE","base64url","bytes","btoa","String","createSiwcAuthorization","options","hostId","clientId","redirectUri","agentName","random","crypto","Uint8Array","attempt","Date","challenge","TextEncoder","url","URL","URLSearchParams","parseSiwcCallback","callback","expected","params","Error","code","exchangeSiwcCode","signal","dependencies","response","withSiwcStage","fetchSiwc","tokenResponseError","tokens","Number","scopes","payload","jwtVerify","createSiwcJWKSet"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { ifInBrowser } from "@midscene/shared/utils";
2
+ import { createLocalJWKSet } from "jose";
3
+ import { createProxyAgentIfNeeded } from "../../proxy/index.mjs";
4
+ async function fetchSiwc(input, init, proxy) {
5
+ if (ifInBrowser) return fetch(input, init);
6
+ const { httpProxy, socksProxy } = proxy ?? {
7
+ httpProxy: process.env.MIDSCENE_MODEL_HTTP_PROXY?.trim() || void 0,
8
+ socksProxy: process.env.MIDSCENE_MODEL_SOCKS_PROXY?.trim() || void 0
9
+ };
10
+ const dispatcher = await createProxyAgentIfNeeded({
11
+ httpProxy,
12
+ socksProxy
13
+ });
14
+ if (!dispatcher) return fetch(input, init);
15
+ try {
16
+ return await fetch(input, {
17
+ ...init,
18
+ dispatcher
19
+ });
20
+ } finally{
21
+ dispatcher.close();
22
+ }
23
+ }
24
+ function createSiwcJWKSet(issuer, signal, request = fetchSiwc) {
25
+ return async (header, token)=>{
26
+ const response = await request(`${issuer}/.well-known/jwks.json`, {
27
+ signal,
28
+ redirect: 'error'
29
+ });
30
+ if (!response.ok) throw new Error(`Failed to fetch OpenAI JWKS: HTTP ${response.status}`);
31
+ return createLocalJWKSet(await response.json())(header, token);
32
+ };
33
+ }
34
+ export { createSiwcJWKSet, fetchSiwc };
35
+
36
+ //# sourceMappingURL=network.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/openai/siwc/network.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/network.ts"],"sourcesContent":["import { ifInBrowser } from '@midscene/shared/utils';\nimport { type JWTVerifyGetKey, createLocalJWKSet } from 'jose';\nimport type { Dispatcher } from 'undici';\nimport { createProxyAgentIfNeeded } from '../../proxy';\n\nexport interface SiwcProxyConfig {\n httpProxy?: string;\n socksProxy?: string;\n}\n\nexport async function fetchSiwc(\n input: Parameters<typeof fetch>[0],\n init?: RequestInit,\n proxy?: SiwcProxyConfig,\n) {\n if (ifInBrowser) {\n return fetch(input, init);\n }\n const { httpProxy, socksProxy } = proxy ?? {\n httpProxy: process.env.MIDSCENE_MODEL_HTTP_PROXY?.trim() || undefined,\n socksProxy: process.env.MIDSCENE_MODEL_SOCKS_PROXY?.trim() || undefined,\n };\n const dispatcher = (await createProxyAgentIfNeeded({\n httpProxy,\n socksProxy,\n })) as Dispatcher | undefined;\n if (!dispatcher) {\n return fetch(input, init);\n }\n try {\n return await fetch(input, { ...init, dispatcher } as RequestInit);\n } finally {\n // Graceful close waits for the response body to be consumed by the caller.\n void dispatcher.close();\n }\n}\n\nexport function createSiwcJWKSet(\n issuer: string,\n signal: AbortSignal,\n request: typeof fetch = fetchSiwc,\n): JWTVerifyGetKey {\n return async (header, token) => {\n const response = await request(`${issuer}/.well-known/jwks.json`, {\n signal,\n redirect: 'error',\n });\n if (!response.ok) {\n throw new Error(`Failed to fetch OpenAI JWKS: HTTP ${response.status}`);\n }\n return createLocalJWKSet(await response.json())(header, token);\n };\n}\n"],"names":["fetchSiwc","input","init","proxy","ifInBrowser","fetch","httpProxy","socksProxy","process","undefined","dispatcher","createProxyAgentIfNeeded","createSiwcJWKSet","issuer","signal","request","header","token","response","Error","createLocalJWKSet"],"mappings":";;;AAUO,eAAeA,UACpBC,KAAkC,EAClCC,IAAkB,EAClBC,KAAuB;IAEvB,IAAIC,aACF,OAAOC,MAAMJ,OAAOC;IAEtB,MAAM,EAAEI,SAAS,EAAEC,UAAU,EAAE,GAAGJ,SAAS;QACzC,WAAWK,QAAQ,GAAG,CAAC,yBAAyB,EAAE,UAAUC;QAC5D,YAAYD,QAAQ,GAAG,CAAC,0BAA0B,EAAE,UAAUC;IAChE;IACA,MAAMC,aAAc,MAAMC,yBAAyB;QACjDL;QACAC;IACF;IACA,IAAI,CAACG,YACH,OAAOL,MAAMJ,OAAOC;IAEtB,IAAI;QACF,OAAO,MAAMG,MAAMJ,OAAO;YAAE,GAAGC,IAAI;YAAEQ;QAAW;IAClD,SAAU;QAEHA,WAAW,KAAK;IACvB;AACF;AAEO,SAASE,iBACdC,MAAc,EACdC,MAAmB,EACnBC,UAAwBf,SAAS;IAEjC,OAAO,OAAOgB,QAAQC;QACpB,MAAMC,WAAW,MAAMH,QAAQ,GAAGF,OAAO,sBAAsB,CAAC,EAAE;YAChEC;YACA,UAAU;QACZ;QACA,IAAI,CAACI,SAAS,EAAE,EACd,MAAM,IAAIC,MAAM,CAAC,kCAAkC,EAAED,SAAS,MAAM,EAAE;QAExE,OAAOE,kBAAkB,MAAMF,SAAS,IAAI,IAAIF,QAAQC;IAC1D;AACF"}
@@ -1,12 +1,14 @@
1
- import { createRemoteJWKSet, jwtVerify } from "jose";
1
+ import { jwtVerify } from "jose";
2
2
  import { tokenResponseError, withSiwcStage } from "./errors.mjs";
3
+ import { createSiwcJWKSet, fetchSiwc } from "./network.mjs";
3
4
  async function refreshSiwcCredentials(saved, signal, dependencies) {
4
5
  const { client_id: clientId, refresh_token: refreshToken } = saved;
5
6
  if (!clientId || !refreshToken) throw new Error('Missing client ID or refresh token.');
7
+ const request = dependencies?.fetch ?? fetchSiwc;
6
8
  const issuer = 'https://auth.openai.com';
7
9
  signal.throwIfAborted();
8
10
  const response = await withSiwcStage('Token refresh', async ()=>{
9
- const response = await (dependencies?.fetch ?? fetch)(`${issuer}/api/accounts/oauth/token`, {
11
+ const response = await request(`${issuer}/api/accounts/oauth/token`, {
10
12
  method: 'POST',
11
13
  headers: {
12
14
  'Content-Type': 'application/x-www-form-urlencoded'
@@ -29,7 +31,7 @@ async function refreshSiwcCredentials(saved, signal, dependencies) {
29
31
  if (!scopes.includes('chatgpt.tokens.use.direct')) throw new Error('ChatGPT plan usage is no longer authorized.');
30
32
  if (void 0 !== tokens.id_token) {
31
33
  if ('string' != typeof tokens.id_token || !tokens.id_token) throw new Error('Invalid refreshed ID token.');
32
- const keys = dependencies?.keys ?? createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`));
34
+ const keys = dependencies?.keys ?? createSiwcJWKSet(issuer, signal, request);
33
35
  const { payload } = await withSiwcStage('ID token verification', ()=>jwtVerify(tokens.id_token, keys, {
34
36
  issuer,
35
37
  audience: saved.client_id,
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/service-caller/openai/siwc/refresh.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/refresh.ts"],"sourcesContent":["import { type JWTVerifyGetKey, createRemoteJWKSet, jwtVerify } from 'jose';\nimport { tokenResponseError, withSiwcStage } from './errors';\ninterface RefreshableCredentials {\n client_id?: string;\n refresh_token?: string;\n subject?: string;\n id_token?: string;\n}\n\nexport async function refreshSiwcCredentials<T extends RefreshableCredentials>(\n saved: T,\n signal: AbortSignal,\n dependencies?: { fetch: typeof fetch; keys: JWTVerifyGetKey },\n) {\n const { client_id: clientId, refresh_token: refreshToken } = saved;\n if (!clientId || !refreshToken) {\n throw new Error('Missing client ID or refresh token.');\n }\n const issuer = 'https://auth.openai.com';\n signal.throwIfAborted();\n const response = await withSiwcStage('Token refresh', async () => {\n const response = await (dependencies?.fetch ?? fetch)(\n `${issuer}/api/accounts/oauth/token`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: refreshToken,\n resource: 'https://api.openai.com/v1',\n }),\n signal,\n redirect: 'error',\n },\n );\n if (!response.ok) {\n throw await tokenResponseError(response);\n }\n return response;\n });\n const tokens = await withSiwcStage('Refresh response parsing', () =>\n response.json(),\n );\n if (\n !tokens ||\n typeof tokens.access_token !== 'string' ||\n !tokens.access_token.trim() ||\n typeof tokens.refresh_token !== 'string' ||\n !tokens.refresh_token.trim() ||\n typeof tokens.expires_in !== 'number' ||\n !Number.isFinite(tokens.expires_in) ||\n tokens.expires_in <= 0 ||\n typeof tokens.token_type !== 'string' ||\n tokens.token_type.toLowerCase() !== 'bearer' ||\n typeof tokens.scope !== 'string'\n ) {\n throw new Error('OpenAI returned an invalid refresh response.');\n }\n const scopes = tokens.scope.split(/\\s+/);\n if (!scopes.includes('chatgpt.tokens.use.direct')) {\n throw new Error('ChatGPT plan usage is no longer authorized.');\n }\n if (tokens.id_token !== undefined) {\n if (typeof tokens.id_token !== 'string' || !tokens.id_token) {\n throw new Error('Invalid refreshed ID token.');\n }\n const keys =\n dependencies?.keys ??\n createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`));\n const { payload } = await withSiwcStage('ID token verification', () =>\n jwtVerify(tokens.id_token, keys, {\n issuer,\n audience: saved.client_id,\n algorithms: ['RS256'],\n // Some CI machines have incorrect system clocks. Do not compare ID token\n // exp/nbf claims against local time when accepting newly returned tokens.\n // Keep signature, issuer, audience and required-claim validation.\n clockTolerance: Number.POSITIVE_INFINITY,\n requiredClaims: ['sub', 'exp', 'iat'],\n }),\n );\n if (payload.sub !== saved.subject) {\n throw new Error('Refreshed identity does not match the saved account.');\n }\n }\n signal.throwIfAborted();\n return {\n ...saved,\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token,\n id_token: tokens.id_token ?? saved.id_token,\n scopes,\n expires_at: Date.now() + tokens.expires_in * 1000,\n };\n}\n"],"names":["refreshSiwcCredentials","saved","signal","dependencies","clientId","refreshToken","Error","issuer","response","withSiwcStage","fetch","URLSearchParams","tokenResponseError","tokens","Number","scopes","undefined","keys","createRemoteJWKSet","URL","payload","jwtVerify","Date"],"mappings":";;AASO,eAAeA,uBACpBC,KAAQ,EACRC,MAAmB,EACnBC,YAA6D;IAE7D,MAAM,EAAE,WAAWC,QAAQ,EAAE,eAAeC,YAAY,EAAE,GAAGJ;IAC7D,IAAI,CAACG,YAAY,CAACC,cAChB,MAAM,IAAIC,MAAM;IAElB,MAAMC,SAAS;IACfL,OAAO,cAAc;IACrB,MAAMM,WAAW,MAAMC,cAAc,iBAAiB;QACpD,MAAMD,WAAW,MAAOL,AAAAA,CAAAA,cAAc,SAASO,KAAI,EACjD,GAAGH,OAAO,yBAAyB,CAAC,EACpC;YACE,QAAQ;YACR,SAAS;gBAAE,gBAAgB;YAAoC;YAC/D,MAAM,IAAII,gBAAgB;gBACxB,YAAY;gBACZ,WAAWP;gBACX,eAAeC;gBACf,UAAU;YACZ;YACAH;YACA,UAAU;QACZ;QAEF,IAAI,CAACM,SAAS,EAAE,EACd,MAAM,MAAMI,mBAAmBJ;QAEjC,OAAOA;IACT;IACA,MAAMK,SAAS,MAAMJ,cAAc,4BAA4B,IAC7DD,SAAS,IAAI;IAEf,IACE,CAACK,UACD,AAA+B,YAA/B,OAAOA,OAAO,YAAY,IAC1B,CAACA,OAAO,YAAY,CAAC,IAAI,MACzB,AAAgC,YAAhC,OAAOA,OAAO,aAAa,IAC3B,CAACA,OAAO,aAAa,CAAC,IAAI,MAC1B,AAA6B,YAA7B,OAAOA,OAAO,UAAU,IACxB,CAACC,OAAO,QAAQ,CAACD,OAAO,UAAU,KAClCA,OAAO,UAAU,IAAI,KACrB,AAA6B,YAA7B,OAAOA,OAAO,UAAU,IACxBA,AAAoC,aAApCA,OAAO,UAAU,CAAC,WAAW,MAC7B,AAAwB,YAAxB,OAAOA,OAAO,KAAK,EAEnB,MAAM,IAAIP,MAAM;IAElB,MAAMS,SAASF,OAAO,KAAK,CAAC,KAAK,CAAC;IAClC,IAAI,CAACE,OAAO,QAAQ,CAAC,8BACnB,MAAM,IAAIT,MAAM;IAElB,IAAIO,AAAoBG,WAApBH,OAAO,QAAQ,EAAgB;QACjC,IAAI,AAA2B,YAA3B,OAAOA,OAAO,QAAQ,IAAiB,CAACA,OAAO,QAAQ,EACzD,MAAM,IAAIP,MAAM;QAElB,MAAMW,OACJd,cAAc,QACde,mBAAmB,IAAIC,IAAI,GAAGZ,OAAO,sBAAsB,CAAC;QAC9D,MAAM,EAAEa,OAAO,EAAE,GAAG,MAAMX,cAAc,yBAAyB,IAC/DY,UAAUR,OAAO,QAAQ,EAAEI,MAAM;gBAC/BV;gBACA,UAAUN,MAAM,SAAS;gBACzB,YAAY;oBAAC;iBAAQ;gBAIrB,gBAAgBa;gBAChB,gBAAgB;oBAAC;oBAAO;oBAAO;iBAAM;YACvC;QAEF,IAAIM,QAAQ,GAAG,KAAKnB,MAAM,OAAO,EAC/B,MAAM,IAAIK,MAAM;IAEpB;IACAJ,OAAO,cAAc;IACrB,OAAO;QACL,GAAGD,KAAK;QACR,cAAcY,OAAO,YAAY;QACjC,eAAeA,OAAO,aAAa;QACnC,UAAUA,OAAO,QAAQ,IAAIZ,MAAM,QAAQ;QAC3Cc;QACA,YAAYO,KAAK,GAAG,KAAKT,AAAoB,OAApBA,OAAO,UAAU;IAC5C;AACF"}
1
+ {"version":3,"file":"ai-model/service-caller/openai/siwc/refresh.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/refresh.ts"],"sourcesContent":["import { type JWTVerifyGetKey, jwtVerify } from 'jose';\nimport { tokenResponseError, withSiwcStage } from './errors';\nimport { createSiwcJWKSet, fetchSiwc } from './network';\ninterface RefreshableCredentials {\n client_id?: string;\n refresh_token?: string;\n subject?: string;\n id_token?: string;\n}\n\nexport async function refreshSiwcCredentials<T extends RefreshableCredentials>(\n saved: T,\n signal: AbortSignal,\n dependencies?: { fetch: typeof fetch; keys?: JWTVerifyGetKey },\n) {\n const { client_id: clientId, refresh_token: refreshToken } = saved;\n if (!clientId || !refreshToken) {\n throw new Error('Missing client ID or refresh token.');\n }\n const request = dependencies?.fetch ?? fetchSiwc;\n const issuer = 'https://auth.openai.com';\n signal.throwIfAborted();\n const response = await withSiwcStage('Token refresh', async () => {\n const response = await request(`${issuer}/api/accounts/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: refreshToken,\n resource: 'https://api.openai.com/v1',\n }),\n signal,\n redirect: 'error',\n });\n if (!response.ok) {\n throw await tokenResponseError(response);\n }\n return response;\n });\n const tokens = await withSiwcStage('Refresh response parsing', () =>\n response.json(),\n );\n if (\n !tokens ||\n typeof tokens.access_token !== 'string' ||\n !tokens.access_token.trim() ||\n typeof tokens.refresh_token !== 'string' ||\n !tokens.refresh_token.trim() ||\n typeof tokens.expires_in !== 'number' ||\n !Number.isFinite(tokens.expires_in) ||\n tokens.expires_in <= 0 ||\n typeof tokens.token_type !== 'string' ||\n tokens.token_type.toLowerCase() !== 'bearer' ||\n typeof tokens.scope !== 'string'\n ) {\n throw new Error('OpenAI returned an invalid refresh response.');\n }\n const scopes = tokens.scope.split(/\\s+/);\n if (!scopes.includes('chatgpt.tokens.use.direct')) {\n throw new Error('ChatGPT plan usage is no longer authorized.');\n }\n if (tokens.id_token !== undefined) {\n if (typeof tokens.id_token !== 'string' || !tokens.id_token) {\n throw new Error('Invalid refreshed ID token.');\n }\n const keys =\n dependencies?.keys ?? createSiwcJWKSet(issuer, signal, request);\n const { payload } = await withSiwcStage('ID token verification', () =>\n jwtVerify(tokens.id_token, keys, {\n issuer,\n audience: saved.client_id,\n algorithms: ['RS256'],\n // Some CI machines have incorrect system clocks. Do not compare ID token\n // exp/nbf claims against local time when accepting newly returned tokens.\n // Keep signature, issuer, audience and required-claim validation.\n clockTolerance: Number.POSITIVE_INFINITY,\n requiredClaims: ['sub', 'exp', 'iat'],\n }),\n );\n if (payload.sub !== saved.subject) {\n throw new Error('Refreshed identity does not match the saved account.');\n }\n }\n signal.throwIfAborted();\n return {\n ...saved,\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token,\n id_token: tokens.id_token ?? saved.id_token,\n scopes,\n expires_at: Date.now() + tokens.expires_in * 1000,\n };\n}\n"],"names":["refreshSiwcCredentials","saved","signal","dependencies","clientId","refreshToken","Error","request","fetchSiwc","issuer","response","withSiwcStage","URLSearchParams","tokenResponseError","tokens","Number","scopes","undefined","keys","createSiwcJWKSet","payload","jwtVerify","Date"],"mappings":";;;AAUO,eAAeA,uBACpBC,KAAQ,EACRC,MAAmB,EACnBC,YAA8D;IAE9D,MAAM,EAAE,WAAWC,QAAQ,EAAE,eAAeC,YAAY,EAAE,GAAGJ;IAC7D,IAAI,CAACG,YAAY,CAACC,cAChB,MAAM,IAAIC,MAAM;IAElB,MAAMC,UAAUJ,cAAc,SAASK;IACvC,MAAMC,SAAS;IACfP,OAAO,cAAc;IACrB,MAAMQ,WAAW,MAAMC,cAAc,iBAAiB;QACpD,MAAMD,WAAW,MAAMH,QAAQ,GAAGE,OAAO,yBAAyB,CAAC,EAAE;YACnE,QAAQ;YACR,SAAS;gBAAE,gBAAgB;YAAoC;YAC/D,MAAM,IAAIG,gBAAgB;gBACxB,YAAY;gBACZ,WAAWR;gBACX,eAAeC;gBACf,UAAU;YACZ;YACAH;YACA,UAAU;QACZ;QACA,IAAI,CAACQ,SAAS,EAAE,EACd,MAAM,MAAMG,mBAAmBH;QAEjC,OAAOA;IACT;IACA,MAAMI,SAAS,MAAMH,cAAc,4BAA4B,IAC7DD,SAAS,IAAI;IAEf,IACE,CAACI,UACD,AAA+B,YAA/B,OAAOA,OAAO,YAAY,IAC1B,CAACA,OAAO,YAAY,CAAC,IAAI,MACzB,AAAgC,YAAhC,OAAOA,OAAO,aAAa,IAC3B,CAACA,OAAO,aAAa,CAAC,IAAI,MAC1B,AAA6B,YAA7B,OAAOA,OAAO,UAAU,IACxB,CAACC,OAAO,QAAQ,CAACD,OAAO,UAAU,KAClCA,OAAO,UAAU,IAAI,KACrB,AAA6B,YAA7B,OAAOA,OAAO,UAAU,IACxBA,AAAoC,aAApCA,OAAO,UAAU,CAAC,WAAW,MAC7B,AAAwB,YAAxB,OAAOA,OAAO,KAAK,EAEnB,MAAM,IAAIR,MAAM;IAElB,MAAMU,SAASF,OAAO,KAAK,CAAC,KAAK,CAAC;IAClC,IAAI,CAACE,OAAO,QAAQ,CAAC,8BACnB,MAAM,IAAIV,MAAM;IAElB,IAAIQ,AAAoBG,WAApBH,OAAO,QAAQ,EAAgB;QACjC,IAAI,AAA2B,YAA3B,OAAOA,OAAO,QAAQ,IAAiB,CAACA,OAAO,QAAQ,EACzD,MAAM,IAAIR,MAAM;QAElB,MAAMY,OACJf,cAAc,QAAQgB,iBAAiBV,QAAQP,QAAQK;QACzD,MAAM,EAAEa,OAAO,EAAE,GAAG,MAAMT,cAAc,yBAAyB,IAC/DU,UAAUP,OAAO,QAAQ,EAAEI,MAAM;gBAC/BT;gBACA,UAAUR,MAAM,SAAS;gBACzB,YAAY;oBAAC;iBAAQ;gBAIrB,gBAAgBc;gBAChB,gBAAgB;oBAAC;oBAAO;oBAAO;iBAAM;YACvC;QAEF,IAAIK,QAAQ,GAAG,KAAKnB,MAAM,OAAO,EAC/B,MAAM,IAAIK,MAAM;IAEpB;IACAJ,OAAO,cAAc;IACrB,OAAO;QACL,GAAGD,KAAK;QACR,cAAca,OAAO,YAAY;QACjC,eAAeA,OAAO,aAAa;QACnC,UAAUA,OAAO,QAAQ,IAAIb,MAAM,QAAQ;QAC3Ce;QACA,YAAYM,KAAK,GAAG,KAAKR,AAAoB,OAApBA,OAAO,UAAU;IAC5C;AACF"}
@@ -1,4 +1,5 @@
1
1
  import { getCredentialStorage } from "./credential-storage.mjs";
2
+ import { fetchSiwc } from "./network.mjs";
2
3
  import { refreshSiwcCredentials } from "./refresh.mjs";
3
4
  function _define_property(obj, key, value) {
4
5
  if (key in obj) Object.defineProperty(obj, key, {
@@ -16,19 +17,21 @@ class SiwcTokenManager {
16
17
  const record = await storage.read();
17
18
  return record.credentials.access_token;
18
19
  }
19
- refreshAccessToken(previousAccessToken) {
20
- this.pendingRefresh ??= this.refreshAndSave(previousAccessToken).finally(()=>{
20
+ refreshAccessToken(previousAccessToken, proxy = {}) {
21
+ this.pendingRefresh ??= this.refreshAndSave(previousAccessToken, proxy).finally(()=>{
21
22
  this.pendingRefresh = void 0;
22
23
  });
23
24
  return this.pendingRefresh;
24
25
  }
25
- async refreshAndSave(previousAccessToken) {
26
+ async refreshAndSave(previousAccessToken, proxy) {
26
27
  const storage = await getCredentialStorage();
27
28
  const record = await storage.read();
28
29
  const saved = record.credentials;
29
30
  if (saved.access_token !== previousAccessToken) return saved.access_token;
30
31
  try {
31
- const credentials = await refreshSiwcCredentials(saved, AbortSignal.timeout(30000));
32
+ const credentials = await refreshSiwcCredentials(saved, AbortSignal.timeout(30000), {
33
+ fetch: (input, init)=>fetchSiwc(input, init, proxy)
34
+ });
32
35
  await storage.save({
33
36
  ...record,
34
37
  updated_at: new Date().toISOString(),
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/service-caller/openai/siwc/token-manager.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/token-manager.ts"],"sourcesContent":["import { getCredentialStorage } from './credential-storage';\nimport { refreshSiwcCredentials } from './refresh';\n\nclass SiwcTokenManager {\n private pendingRefresh?: Promise<string>;\n\n async getAccessToken(): Promise<string> {\n const storage = await getCredentialStorage();\n const record = await storage.read();\n // Some CI machines have incorrect system clocks. Let the server reject\n // expired tokens instead of comparing expires_at against local time.\n return record.credentials.access_token;\n }\n\n /** Only refresh operations are shared; ordinary reads remain independent. */\n refreshAccessToken(previousAccessToken: string): Promise<string> {\n this.pendingRefresh ??= this.refreshAndSave(previousAccessToken).finally(\n () => {\n this.pendingRefresh = undefined;\n },\n );\n return this.pendingRefresh;\n }\n\n private async refreshAndSave(previousAccessToken: string): Promise<string> {\n // Re-read after acquiring the refresh slot: another caller may have saved\n // a new token since this request first read its credentials.\n const storage = await getCredentialStorage();\n const record = await storage.read();\n const saved = record.credentials;\n if (saved.access_token !== previousAccessToken) {\n return saved.access_token;\n }\n try {\n const credentials = await refreshSiwcCredentials(\n saved,\n AbortSignal.timeout(30_000),\n );\n await storage.save({\n ...record,\n updated_at: new Date().toISOString(),\n credentials,\n });\n return credentials.access_token;\n } catch (cause) {\n throw new Error(\n `Unable to refresh SIWC credentials: ${cause instanceof Error ? cause.message : String(cause)} ${storage.reauthorizationHint}`,\n { cause },\n );\n }\n }\n}\n\nexport const siwcTokenManager = new SiwcTokenManager();\n"],"names":["SiwcTokenManager","storage","getCredentialStorage","record","previousAccessToken","undefined","saved","credentials","refreshSiwcCredentials","AbortSignal","Date","cause","Error","String","siwcTokenManager"],"mappings":";;;;;;;;;;;;AAGA,MAAMA;IAGJ,MAAM,iBAAkC;QACtC,MAAMC,UAAU,MAAMC;QACtB,MAAMC,SAAS,MAAMF,QAAQ,IAAI;QAGjC,OAAOE,OAAO,WAAW,CAAC,YAAY;IACxC;IAGA,mBAAmBC,mBAA2B,EAAmB;QAC/D,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,cAAc,CAACA,qBAAqB,OAAO,CACtE;YACE,IAAI,CAAC,cAAc,GAAGC;QACxB;QAEF,OAAO,IAAI,CAAC,cAAc;IAC5B;IAEA,MAAc,eAAeD,mBAA2B,EAAmB;QAGzE,MAAMH,UAAU,MAAMC;QACtB,MAAMC,SAAS,MAAMF,QAAQ,IAAI;QACjC,MAAMK,QAAQH,OAAO,WAAW;QAChC,IAAIG,MAAM,YAAY,KAAKF,qBACzB,OAAOE,MAAM,YAAY;QAE3B,IAAI;YACF,MAAMC,cAAc,MAAMC,uBACxBF,OACAG,YAAY,OAAO,CAAC;YAEtB,MAAMR,QAAQ,IAAI,CAAC;gBACjB,GAAGE,MAAM;gBACT,YAAY,IAAIO,OAAO,WAAW;gBAClCH;YACF;YACA,OAAOA,YAAY,YAAY;QACjC,EAAE,OAAOI,OAAO;YACd,MAAM,IAAIC,MACR,CAAC,oCAAoC,EAAED,iBAAiBC,QAAQD,MAAM,OAAO,GAAGE,OAAOF,OAAO,CAAC,EAAEV,QAAQ,mBAAmB,EAAE,EAC9H;gBAAEU;YAAM;QAEZ;IACF;;QA9CA,uBAAQ,kBAAR;;AA+CF;AAEO,MAAMG,mBAAmB,IAAId"}
1
+ {"version":3,"file":"ai-model/service-caller/openai/siwc/token-manager.mjs","sources":["../../../../../../src/ai-model/service-caller/openai/siwc/token-manager.ts"],"sourcesContent":["import { getCredentialStorage } from './credential-storage';\nimport { type SiwcProxyConfig, fetchSiwc } from './network';\nimport { refreshSiwcCredentials } from './refresh';\n\nclass SiwcTokenManager {\n private pendingRefresh?: Promise<string>;\n\n async getAccessToken(): Promise<string> {\n const storage = await getCredentialStorage();\n const record = await storage.read();\n // Some CI machines have incorrect system clocks. Let the server reject\n // expired tokens instead of comparing expires_at against local time.\n return record.credentials.access_token;\n }\n\n /** Only refresh operations are shared; ordinary reads remain independent. */\n refreshAccessToken(\n previousAccessToken: string,\n proxy: SiwcProxyConfig = {},\n ): Promise<string> {\n this.pendingRefresh ??= this.refreshAndSave(\n previousAccessToken,\n proxy,\n ).finally(() => {\n this.pendingRefresh = undefined;\n });\n return this.pendingRefresh;\n }\n\n private async refreshAndSave(\n previousAccessToken: string,\n proxy: SiwcProxyConfig,\n ): Promise<string> {\n // Re-read after acquiring the refresh slot: another caller may have saved\n // a new token since this request first read its credentials.\n const storage = await getCredentialStorage();\n const record = await storage.read();\n const saved = record.credentials;\n if (saved.access_token !== previousAccessToken) {\n return saved.access_token;\n }\n try {\n const credentials = await refreshSiwcCredentials(\n saved,\n AbortSignal.timeout(30_000),\n { fetch: (input, init) => fetchSiwc(input, init, proxy) },\n );\n await storage.save({\n ...record,\n updated_at: new Date().toISOString(),\n credentials,\n });\n return credentials.access_token;\n } catch (cause) {\n throw new Error(\n `Unable to refresh SIWC credentials: ${cause instanceof Error ? cause.message : String(cause)} ${storage.reauthorizationHint}`,\n { cause },\n );\n }\n }\n}\n\nexport const siwcTokenManager = new SiwcTokenManager();\n"],"names":["SiwcTokenManager","storage","getCredentialStorage","record","previousAccessToken","proxy","undefined","saved","credentials","refreshSiwcCredentials","AbortSignal","input","init","fetchSiwc","Date","cause","Error","String","siwcTokenManager"],"mappings":";;;;;;;;;;;;;AAIA,MAAMA;IAGJ,MAAM,iBAAkC;QACtC,MAAMC,UAAU,MAAMC;QACtB,MAAMC,SAAS,MAAMF,QAAQ,IAAI;QAGjC,OAAOE,OAAO,WAAW,CAAC,YAAY;IACxC;IAGA,mBACEC,mBAA2B,EAC3BC,QAAyB,CAAC,CAAC,EACV;QACjB,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,cAAc,CACzCD,qBACAC,OACA,OAAO,CAAC;YACR,IAAI,CAAC,cAAc,GAAGC;QACxB;QACA,OAAO,IAAI,CAAC,cAAc;IAC5B;IAEA,MAAc,eACZF,mBAA2B,EAC3BC,KAAsB,EACL;QAGjB,MAAMJ,UAAU,MAAMC;QACtB,MAAMC,SAAS,MAAMF,QAAQ,IAAI;QACjC,MAAMM,QAAQJ,OAAO,WAAW;QAChC,IAAII,MAAM,YAAY,KAAKH,qBACzB,OAAOG,MAAM,YAAY;QAE3B,IAAI;YACF,MAAMC,cAAc,MAAMC,uBACxBF,OACAG,YAAY,OAAO,CAAC,QACpB;gBAAE,OAAO,CAACC,OAAOC,OAASC,UAAUF,OAAOC,MAAMP;YAAO;YAE1D,MAAMJ,QAAQ,IAAI,CAAC;gBACjB,GAAGE,MAAM;gBACT,YAAY,IAAIW,OAAO,WAAW;gBAClCN;YACF;YACA,OAAOA,YAAY,YAAY;QACjC,EAAE,OAAOO,OAAO;YACd,MAAM,IAAIC,MACR,CAAC,oCAAoC,EAAED,iBAAiBC,QAAQD,MAAM,OAAO,GAAGE,OAAOF,OAAO,CAAC,EAAEd,QAAQ,mBAAmB,EAAE,EAC9H;gBAAEc;YAAM;QAEZ;IACF;;QAtDA,uBAAQ,kBAAR;;AAuDF;AAEO,MAAMG,mBAAmB,IAAIlB"}
@@ -1,9 +1,13 @@
1
1
  import { jsonrepair } from "jsonrepair";
2
2
  function extractJSONFromCodeBlock(response) {
3
3
  try {
4
- const jsonMatch = response.match(/^\s*(\{[\s\S]*\})\s*$/);
4
+ JSON.parse(response);
5
+ return response.trim();
6
+ } catch {}
7
+ try {
8
+ const jsonMatch = response.match(/^\s*(\{[\s\S]*\}|\[[\s\S]*\])\s*$/);
5
9
  if (jsonMatch) return jsonMatch[1];
6
- const codeBlockMatch = response.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
10
+ const codeBlockMatch = response.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
7
11
  if (codeBlockMatch) return codeBlockMatch[1];
8
12
  const jsonLikeMatch = response.match(/\{[\s\S]*\}/);
9
13
  if (jsonLikeMatch) return jsonLikeMatch[0];
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/shared/json.mjs","sources":["../../../../src/ai-model/shared/json.ts"],"sourcesContent":["import { jsonrepair } from 'jsonrepair';\n\n/**\n * Extract the JSON portion from a model response.\n *\n * This mainly handles fenced JSON like ```json { ... } ``` by removing the\n * fence. If that does not produce a JSON object, it falls back to a greedy regex\n * that extracts from the first \"{\" to the last \"}\". If that still fails, the\n * original response is returned.\n *\n * Expected model responses are JSON objects, possibly wrapped in markdown\n * fences. Natural language mixed with JSON, or arrays like\n * [{\"type\":\"Tap\"}, {\"type\":\"Hover\"}], are outside the supported contract and\n * are not reliably recoverable.\n *\n * This extractor is also used by extraction responses that can be any\n * JSON value. In those cases, arrays/strings/numbers have no object braces and\n * pass through unchanged.\n */\nexport function extractJSONFromCodeBlock(response: string) {\n try {\n // First, try to match a JSON object directly in the response\n const jsonMatch = response.match(/^\\s*(\\{[\\s\\S]*\\})\\s*$/);\n if (jsonMatch) {\n return jsonMatch[1];\n }\n\n // If no direct JSON object is found, try to extract JSON from a code block\n const codeBlockMatch = response.match(\n /```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/,\n );\n if (codeBlockMatch) {\n return codeBlockMatch[1];\n }\n\n // If no code block is found, try to find a JSON-like structure in the text\n const jsonLikeMatch = response.match(/\\{[\\s\\S]*\\}/);\n if (jsonLikeMatch) {\n return jsonLikeMatch[0];\n }\n } catch {}\n // If no JSON-like structure is found, return the original response\n return response;\n}\n\nexport type JsonParserSource =\n | 'generic-object'\n | 'planning-action-param'\n | 'locate'\n | 'section-locator';\n\nexport interface JsonParserContext {\n source: JsonParserSource;\n preserveStringValueKeys?: string[];\n}\n\nexport type JsonParser = (raw: string, context?: JsonParserContext) => unknown;\n\n/**\n * Trim whitespace in parsed JSON by normalizing:\n * 1. All object keys (e.g., \" prompt \" -> \"prompt\")\n * 2. All string values (e.g., \" Tap \" -> \"Tap\")\n * This handles LLM output that may include leading/trailing spaces.\n */\nfunction trimParsedJsonStrings(\n obj: any,\n context: Pick<JsonParserContext, 'preserveStringValueKeys'> = {},\n): any {\n // Handle null and undefined\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n // Handle arrays - recursively normalize each element\n if (Array.isArray(obj)) {\n return obj.map((item) => trimParsedJsonStrings(item, context));\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const normalized: any = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Trim the key to remove leading/trailing spaces\n const trimmedKey = key.trim();\n const preserveStringValue =\n context.preserveStringValueKeys?.includes(trimmedKey) ?? false;\n\n const normalizedValue =\n typeof value === 'string'\n ? preserveStringValue\n ? value\n : value.trim()\n : trimParsedJsonStrings(value, context);\n\n normalized[trimmedKey] = normalizedValue;\n }\n\n return normalized;\n }\n\n // Handle primitive strings\n if (typeof obj === 'string') {\n return obj.trim();\n }\n\n // Return other primitives as-is\n return obj;\n}\n\nfunction repairKnownJsonIssues(\n jsonBlock: string,\n _rawResponse: string,\n): string {\n // TODO: Add project-specific repairs that jsonrepair cannot handle.\n return jsonBlock;\n}\n\nexport function assertJsonObject(\n parsed: unknown,\n): asserts parsed is Record<string, unknown> {\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n const parsedType = Array.isArray(parsed)\n ? 'array'\n : parsed === null\n ? 'null'\n : typeof parsed;\n throw new Error(\n `LLM response is valid JSON but does not match the expected schema. Expected to be a JSON object, got ${parsedType}: ${JSON.stringify(parsed)}.`,\n );\n }\n}\n\nfunction parseJsonWithRepair(jsonStr: string) {\n try {\n return JSON.parse(jsonStr);\n } catch {\n return JSON.parse(jsonrepair(jsonStr));\n }\n}\n\nexport function parseModelResponseJson(\n raw: string,\n context?: JsonParserContext,\n) {\n const cleanJsonString = extractJSONFromCodeBlock(raw);\n\n let parsedObj: unknown;\n\n try {\n parsedObj = parseJsonWithRepair(cleanJsonString);\n } catch (e1) {\n const code = repairKnownJsonIssues(cleanJsonString, raw);\n if (code === cleanJsonString) {\n throw new Error(\n `failed to parse LLM response into JSON. Error - ${String(\n e1,\n )}. Response - \\n ${raw}`,\n );\n }\n\n try {\n parsedObj = parseJsonWithRepair(code);\n } catch (e2) {\n throw new Error(\n `failed to parse LLM response into JSON. First error - ${String(\n e1,\n )}. Second error - ${String(e2)}. Response - \\n ${raw}`,\n );\n }\n }\n\n return trimParsedJsonStrings(parsedObj, context);\n}\n"],"names":["extractJSONFromCodeBlock","response","jsonMatch","codeBlockMatch","jsonLikeMatch","trimParsedJsonStrings","obj","context","Array","item","normalized","key","value","Object","trimmedKey","preserveStringValue","normalizedValue","repairKnownJsonIssues","jsonBlock","_rawResponse","assertJsonObject","parsed","parsedType","Error","JSON","parseJsonWithRepair","jsonStr","jsonrepair","parseModelResponseJson","raw","cleanJsonString","parsedObj","e1","code","String","e2"],"mappings":";AAmBO,SAASA,yBAAyBC,QAAgB;IACvD,IAAI;QAEF,MAAMC,YAAYD,SAAS,KAAK,CAAC;QACjC,IAAIC,WACF,OAAOA,SAAS,CAAC,EAAE;QAIrB,MAAMC,iBAAiBF,SAAS,KAAK,CACnC;QAEF,IAAIE,gBACF,OAAOA,cAAc,CAAC,EAAE;QAI1B,MAAMC,gBAAgBH,SAAS,KAAK,CAAC;QACrC,IAAIG,eACF,OAAOA,aAAa,CAAC,EAAE;IAE3B,EAAE,OAAM,CAAC;IAET,OAAOH;AACT;AAqBA,SAASI,sBACPC,GAAQ,EACRC,UAA8D,CAAC,CAAC;IAGhE,IAAID,QAAAA,KACF,OAAOA;IAIT,IAAIE,MAAM,OAAO,CAACF,MAChB,OAAOA,IAAI,GAAG,CAAC,CAACG,OAASJ,sBAAsBI,MAAMF;IAIvD,IAAI,AAAe,YAAf,OAAOD,KAAkB;QAC3B,MAAMI,aAAkB,CAAC;QAEzB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAO,OAAO,CAACP,KAAM;YAE9C,MAAMQ,aAAaH,IAAI,IAAI;YAC3B,MAAMI,sBACJR,QAAQ,uBAAuB,EAAE,SAASO,eAAe;YAE3D,MAAME,kBACJ,AAAiB,YAAjB,OAAOJ,QACHG,sBACEH,QACAA,MAAM,IAAI,KACZP,sBAAsBO,OAAOL;YAEnCG,UAAU,CAACI,WAAW,GAAGE;QAC3B;QAEA,OAAON;IACT;IAGA,IAAI,AAAe,YAAf,OAAOJ,KACT,OAAOA,IAAI,IAAI;IAIjB,OAAOA;AACT;AAEA,SAASW,sBACPC,SAAiB,EACjBC,YAAoB;IAGpB,OAAOD;AACT;AAEO,SAASE,iBACdC,MAAe;IAEf,IAAI,CAACA,UAAU,AAAkB,YAAlB,OAAOA,UAAuBb,MAAM,OAAO,CAACa,SAAS;QAClE,MAAMC,aAAad,MAAM,OAAO,CAACa,UAC7B,UACAA,AAAW,SAAXA,SACE,SACA,OAAOA;QACb,MAAM,IAAIE,MACR,CAAC,qGAAqG,EAAED,WAAW,EAAE,EAAEE,KAAK,SAAS,CAACH,QAAQ,CAAC,CAAC;IAEpJ;AACF;AAEA,SAASI,oBAAoBC,OAAe;IAC1C,IAAI;QACF,OAAOF,KAAK,KAAK,CAACE;IACpB,EAAE,OAAM;QACN,OAAOF,KAAK,KAAK,CAACG,WAAWD;IAC/B;AACF;AAEO,SAASE,uBACdC,GAAW,EACXtB,OAA2B;IAE3B,MAAMuB,kBAAkB9B,yBAAyB6B;IAEjD,IAAIE;IAEJ,IAAI;QACFA,YAAYN,oBAAoBK;IAClC,EAAE,OAAOE,IAAI;QACX,MAAMC,OAAOhB,sBAAsBa,iBAAiBD;QACpD,IAAII,SAASH,iBACX,MAAM,IAAIP,MACR,CAAC,gDAAgD,EAAEW,OACjDF,IACA,gBAAgB,EAAEH,KAAK;QAI7B,IAAI;YACFE,YAAYN,oBAAoBQ;QAClC,EAAE,OAAOE,IAAI;YACX,MAAM,IAAIZ,MACR,CAAC,sDAAsD,EAAEW,OACvDF,IACA,iBAAiB,EAAEE,OAAOC,IAAI,gBAAgB,EAAEN,KAAK;QAE3D;IACF;IAEA,OAAOxB,sBAAsB0B,WAAWxB;AAC1C"}
1
+ {"version":3,"file":"ai-model/shared/json.mjs","sources":["../../../../src/ai-model/shared/json.ts"],"sourcesContent":["import { jsonrepair } from 'jsonrepair';\n\n/**\n * Preserve complete JSON values and remove optional markdown fences before\n * parsing. Keep array boundaries intact, including when the contents need repair.\n * For prose mixed with JSON, fall back to extracting the first through last\n * object brace; arbitrary prose with arrays is not a supported format.\n */\nexport function extractJSONFromCodeBlock(response: string) {\n try {\n JSON.parse(response);\n return response.trim();\n } catch {\n // Invalid JSON may still be recoverable after extraction and repair.\n }\n\n try {\n // Preserve complete object and array boundaries for jsonrepair.\n const jsonMatch = response.match(/^\\s*(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])\\s*$/);\n if (jsonMatch) {\n return jsonMatch[1];\n }\n\n // If no direct JSON object is found, try to extract JSON from a code block\n const codeBlockMatch = response.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/i);\n if (codeBlockMatch) {\n return codeBlockMatch[1];\n }\n\n // If no code block is found, try to find a JSON-like structure in the text\n const jsonLikeMatch = response.match(/\\{[\\s\\S]*\\}/);\n if (jsonLikeMatch) {\n return jsonLikeMatch[0];\n }\n } catch {}\n // If no JSON-like structure is found, return the original response\n return response;\n}\n\nexport type JsonParserSource =\n | 'generic-object'\n | 'planning-action-param'\n | 'locate'\n | 'section-locator';\n\nexport interface JsonParserContext {\n source: JsonParserSource;\n preserveStringValueKeys?: string[];\n}\n\nexport type JsonParser = (raw: string, context?: JsonParserContext) => unknown;\n\n/**\n * Trim whitespace in parsed JSON by normalizing:\n * 1. All object keys (e.g., \" prompt \" -> \"prompt\")\n * 2. All string values (e.g., \" Tap \" -> \"Tap\")\n * This handles LLM output that may include leading/trailing spaces.\n */\nfunction trimParsedJsonStrings(\n obj: any,\n context: Pick<JsonParserContext, 'preserveStringValueKeys'> = {},\n): any {\n // Handle null and undefined\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n // Handle arrays - recursively normalize each element\n if (Array.isArray(obj)) {\n return obj.map((item) => trimParsedJsonStrings(item, context));\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const normalized: any = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Trim the key to remove leading/trailing spaces\n const trimmedKey = key.trim();\n const preserveStringValue =\n context.preserveStringValueKeys?.includes(trimmedKey) ?? false;\n\n const normalizedValue =\n typeof value === 'string'\n ? preserveStringValue\n ? value\n : value.trim()\n : trimParsedJsonStrings(value, context);\n\n normalized[trimmedKey] = normalizedValue;\n }\n\n return normalized;\n }\n\n // Handle primitive strings\n if (typeof obj === 'string') {\n return obj.trim();\n }\n\n // Return other primitives as-is\n return obj;\n}\n\nfunction repairKnownJsonIssues(\n jsonBlock: string,\n _rawResponse: string,\n): string {\n // TODO: Add project-specific repairs that jsonrepair cannot handle.\n return jsonBlock;\n}\n\nexport function assertJsonObject(\n parsed: unknown,\n): asserts parsed is Record<string, unknown> {\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n const parsedType = Array.isArray(parsed)\n ? 'array'\n : parsed === null\n ? 'null'\n : typeof parsed;\n throw new Error(\n `LLM response is valid JSON but does not match the expected schema. Expected to be a JSON object, got ${parsedType}: ${JSON.stringify(parsed)}.`,\n );\n }\n}\n\nfunction parseJsonWithRepair(jsonStr: string) {\n try {\n return JSON.parse(jsonStr);\n } catch {\n return JSON.parse(jsonrepair(jsonStr));\n }\n}\n\nexport function parseModelResponseJson(\n raw: string,\n context?: JsonParserContext,\n) {\n const cleanJsonString = extractJSONFromCodeBlock(raw);\n\n let parsedObj: unknown;\n\n try {\n parsedObj = parseJsonWithRepair(cleanJsonString);\n } catch (e1) {\n const code = repairKnownJsonIssues(cleanJsonString, raw);\n if (code === cleanJsonString) {\n throw new Error(\n `failed to parse LLM response into JSON. Error - ${String(\n e1,\n )}. Response - \\n ${raw}`,\n );\n }\n\n try {\n parsedObj = parseJsonWithRepair(code);\n } catch (e2) {\n throw new Error(\n `failed to parse LLM response into JSON. First error - ${String(\n e1,\n )}. Second error - ${String(e2)}. Response - \\n ${raw}`,\n );\n }\n }\n\n return trimParsedJsonStrings(parsedObj, context);\n}\n"],"names":["extractJSONFromCodeBlock","response","JSON","jsonMatch","codeBlockMatch","jsonLikeMatch","trimParsedJsonStrings","obj","context","Array","item","normalized","key","value","Object","trimmedKey","preserveStringValue","normalizedValue","repairKnownJsonIssues","jsonBlock","_rawResponse","assertJsonObject","parsed","parsedType","Error","parseJsonWithRepair","jsonStr","jsonrepair","parseModelResponseJson","raw","cleanJsonString","parsedObj","e1","code","String","e2"],"mappings":";AAQO,SAASA,yBAAyBC,QAAgB;IACvD,IAAI;QACFC,KAAK,KAAK,CAACD;QACX,OAAOA,SAAS,IAAI;IACtB,EAAE,OAAM,CAER;IAEA,IAAI;QAEF,MAAME,YAAYF,SAAS,KAAK,CAAC;QACjC,IAAIE,WACF,OAAOA,SAAS,CAAC,EAAE;QAIrB,MAAMC,iBAAiBH,SAAS,KAAK,CAAC;QACtC,IAAIG,gBACF,OAAOA,cAAc,CAAC,EAAE;QAI1B,MAAMC,gBAAgBJ,SAAS,KAAK,CAAC;QACrC,IAAII,eACF,OAAOA,aAAa,CAAC,EAAE;IAE3B,EAAE,OAAM,CAAC;IAET,OAAOJ;AACT;AAqBA,SAASK,sBACPC,GAAQ,EACRC,UAA8D,CAAC,CAAC;IAGhE,IAAID,QAAAA,KACF,OAAOA;IAIT,IAAIE,MAAM,OAAO,CAACF,MAChB,OAAOA,IAAI,GAAG,CAAC,CAACG,OAASJ,sBAAsBI,MAAMF;IAIvD,IAAI,AAAe,YAAf,OAAOD,KAAkB;QAC3B,MAAMI,aAAkB,CAAC;QAEzB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAO,OAAO,CAACP,KAAM;YAE9C,MAAMQ,aAAaH,IAAI,IAAI;YAC3B,MAAMI,sBACJR,QAAQ,uBAAuB,EAAE,SAASO,eAAe;YAE3D,MAAME,kBACJ,AAAiB,YAAjB,OAAOJ,QACHG,sBACEH,QACAA,MAAM,IAAI,KACZP,sBAAsBO,OAAOL;YAEnCG,UAAU,CAACI,WAAW,GAAGE;QAC3B;QAEA,OAAON;IACT;IAGA,IAAI,AAAe,YAAf,OAAOJ,KACT,OAAOA,IAAI,IAAI;IAIjB,OAAOA;AACT;AAEA,SAASW,sBACPC,SAAiB,EACjBC,YAAoB;IAGpB,OAAOD;AACT;AAEO,SAASE,iBACdC,MAAe;IAEf,IAAI,CAACA,UAAU,AAAkB,YAAlB,OAAOA,UAAuBb,MAAM,OAAO,CAACa,SAAS;QAClE,MAAMC,aAAad,MAAM,OAAO,CAACa,UAC7B,UACAA,AAAW,SAAXA,SACE,SACA,OAAOA;QACb,MAAM,IAAIE,MACR,CAAC,qGAAqG,EAAED,WAAW,EAAE,EAAErB,KAAK,SAAS,CAACoB,QAAQ,CAAC,CAAC;IAEpJ;AACF;AAEA,SAASG,oBAAoBC,OAAe;IAC1C,IAAI;QACF,OAAOxB,KAAK,KAAK,CAACwB;IACpB,EAAE,OAAM;QACN,OAAOxB,KAAK,KAAK,CAACyB,WAAWD;IAC/B;AACF;AAEO,SAASE,uBACdC,GAAW,EACXrB,OAA2B;IAE3B,MAAMsB,kBAAkB9B,yBAAyB6B;IAEjD,IAAIE;IAEJ,IAAI;QACFA,YAAYN,oBAAoBK;IAClC,EAAE,OAAOE,IAAI;QACX,MAAMC,OAAOf,sBAAsBY,iBAAiBD;QACpD,IAAII,SAASH,iBACX,MAAM,IAAIN,MACR,CAAC,gDAAgD,EAAEU,OACjDF,IACA,gBAAgB,EAAEH,KAAK;QAI7B,IAAI;YACFE,YAAYN,oBAAoBQ;QAClC,EAAE,OAAOE,IAAI;YACX,MAAM,IAAIX,MACR,CAAC,sDAAsD,EAAEU,OACvDF,IACA,iBAAiB,EAAEE,OAAOC,IAAI,gBAAgB,EAAEN,KAAK;QAE3D;IACF;IAEA,OAAOvB,sBAAsByB,WAAWvB;AAC1C"}