@gitkraken/core-gitlens 0.24.0 → 0.25.0
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.
- package/CHANGELOG.md +27 -1
- package/README.md +23 -15
- package/dist/git/errors.d.ts +6 -5
- package/dist/git/errors.js +7 -6
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +1 -1
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/remoteProvider.d.ts +4 -1
- package/dist/git/models/remoteProvider.js.map +1 -1
- package/dist/git/providers/provider.d.ts +4 -2
- package/dist/git/utils/branch.utils.d.ts +4 -1
- package/dist/git/utils/branch.utils.js +5 -2
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/mergeConflicts.utils.js +2 -2
- package/dist/git/utils/mergeConflicts.utils.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +14 -3
- package/dist/git-cli/cliGitProvider.js +71 -34
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +7 -1
- package/dist/git-cli/exec/git.js +19 -4
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/providers/branchRemote.js +2 -5
- package/dist/git-cli/providers/branchRemote.js.map +1 -1
- package/dist/git-cli/providers/branches.js +4 -4
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.js +4 -1
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/plus/ai/errors.d.ts +4 -1
- package/dist/plus/ai/errors.js +4 -0
- package/dist/plus/ai/errors.js.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.js +1 -1
- package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -0
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
- package/dist/plus/git-github/api/github.js +12 -13
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/token.d.ts +2 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +6 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +2 -2
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +18 -5
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +3 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +4 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
- package/dist/plus/integrations/authentication/models.d.ts +2 -0
- package/dist/plus/integrations/authentication/models.js +2 -1
- package/dist/plus/integrations/authentication/models.js.map +1 -1
- package/dist/plus/integrations/authentication/observedAuthenticationProvider.js +1 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js +1 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +6 -0
- package/dist/plus/integrations/integrationService.js +3 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.js +1 -1
- package/dist/plus/integrations/manager.d.ts +51 -3
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +14 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +50 -41
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +27 -5
- package/dist/plus/integrations/models/integration.js +57 -15
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +6 -0
- package/dist/plus/integrations/models/issuesIntegration.js +11 -11
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/github.js +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.js +2 -2
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +1 -1
- package/dist/plus/integrations/providers/jira-server.js +2 -2
- package/dist/plus/integrations/providers/jira.js +1 -1
- package/dist/plus/integrations/providers/linear.js +2 -2
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.js +2 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.js +9 -0
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +1 -1
- package/dist/plus/integrations/providers/trello.js +1 -1
- package/dist/plus/integrations/providers/utils.js +1 -1
- package/dist/plus/integrations/reads/counts.js +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +5 -4
- package/dist/plus/integrations/reads/drains.js +28 -8
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -0
- package/dist/plus/integrations/reads/issues.js +5 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +5 -6
- package/dist/plus/integrations/reads/paging.js +5 -6
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts +4 -0
- package/dist/plus/integrations/reads/pullRequests.js +4 -2
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js +42 -10
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +23 -3
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/notifications/api.d.ts +20 -0
- package/dist/plus/notifications/api.js +89 -0
- package/dist/plus/notifications/api.js.map +1 -0
- package/dist/plus/notifications/context.d.ts +28 -0
- package/dist/plus/notifications/context.js +0 -0
- package/dist/plus/notifications/errors.d.ts +13 -0
- package/dist/plus/notifications/errors.js +22 -0
- package/dist/plus/notifications/errors.js.map +1 -0
- package/dist/plus/notifications/models.d.ts +94 -0
- package/dist/plus/notifications/models.js +0 -0
- package/dist/plus/notifications/notificationsService.d.ts +115 -0
- package/dist/plus/notifications/notificationsService.js +381 -0
- package/dist/plus/notifications/notificationsService.js.map +1 -0
- package/dist/plus/notifications/parse.d.ts +12 -0
- package/dist/plus/notifications/parse.js +152 -0
- package/dist/plus/notifications/parse.js.map +1 -0
- package/dist/plus/notifications/presentation.d.ts +28 -0
- package/dist/plus/notifications/presentation.js +55 -0
- package/dist/plus/notifications/presentation.js.map +1 -0
- package/dist/utils/env/node/exec.d.ts +10 -1
- package/dist/utils/env/node/exec.js +39 -2
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +21 -2
- package/docs/notifications.md +239 -0
- package/package.json +21 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openAICompatibleProviderBase.js","names":[],"sources":["../../../../../plus/ai/src/providers/openAICompatibleProviderBase.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\nimport { CancellationError, isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uuid } from '@gitlens/utils/crypto.js';\nimport { getLoggableName, Logger } from '@gitlens/utils/logger.js';\nimport { maybeStartScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { AIProviders } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel, AIProviderDescriptor } from '../models/model.js';\nimport type {\n\tAIChatMessage,\n\tAIChatMessageRole,\n\tAIFinishReason,\n\tAIProvider,\n\tAIProviderResponse,\n\tAIResponseFormat,\n\tAIToolCall,\n\tAIToolDefinition,\n\tJSONSchema,\n} from '../models/provider.js';\nimport { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from '../utils/ai.utils.js';\nimport type { AIProviderContext } from './context.js';\nimport { isResponseFormatRejected, rememberResponseFormatRejection } from './responseFormatCache.js';\n\n// Native-format rejection wording varies by provider (\"response_format\", \"structured outputs\",\n// Gemini's \"Json mode is not enabled\", and its OpenAI-compat layer's `generation_config.response_schema`\n// path in INVALID_ARGUMENT bodies, ...)\nconst formatRejectionRegex =\n\t/response[_ ]?(?:format|schema)|structured[_ ]?output|output_config|json_schema|json[_ ]?mode/i;\n\nfunction getRequestErrorMessage(action: AIActionType, providerName: string, message: string): string {\n\tconst args = { provider: providerName, message: message };\n\tswitch (action) {\n\t\tcase 'explain-changes':\n\t\t\treturn l10n.t('Unable to Explain Changes: ({provider}) {message}', args);\n\t\tcase 'review-changes':\n\t\t\treturn l10n.t('Unable to Review Changes: ({provider}) {message}', args);\n\t\tcase 'generate-commitMessage':\n\t\t\treturn l10n.t('Unable to Generate Commit Message: ({provider}) {message}', args);\n\t\tcase 'generate-stashMessage':\n\t\t\treturn l10n.t('Unable to Generate Stash Message: ({provider}) {message}', args);\n\t\tcase 'generate-changelog':\n\t\t\treturn l10n.t('Unable to Generate Changelog: ({provider}) {message}', args);\n\t\tcase 'generate-create-cloudPatch':\n\t\t\treturn l10n.t('Unable to Create Cloud Patch Details: ({provider}) {message}', args);\n\t\tcase 'generate-create-pullRequest':\n\t\t\treturn l10n.t('Unable to Create Pull Request Details: ({provider}) {message}', args);\n\t\tcase 'generate-commits':\n\t\t\treturn l10n.t('Unable to Generate Commits: ({provider}) {message}', args);\n\t\tcase 'conflict-resolution':\n\t\t\treturn l10n.t('Unable to Resolve Conflicts (Preview): ({provider}) {message}', args);\n\t\tcase 'generate-searchQuery':\n\t\t\treturn l10n.t('Unable to Generate Search Query: ({provider}) {message}', args);\n\t}\n}\n\nexport abstract class OpenAICompatibleProviderBase<T extends AIProviders> implements AIProvider<T> {\n\tconstructor(protected readonly context: AIProviderContext) {}\n\n\tdispose(): void {}\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tabstract readonly id: T;\n\tabstract readonly name: string;\n\tprotected abstract readonly descriptor: AIProviderDescriptor<T>;\n\tprotected abstract readonly config: { keyUrl?: string; keyValidator?: RegExp };\n\n\tasync configured(silent: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst apiKey = await this.getApiKey(silent);\n\t\t\treturn apiKey != null;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) return false;\n\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\tasync getApiKey(silent: boolean): Promise<string | undefined> {\n\t\tconst orgConf = this.context.getProviderConfig(this.id);\n\t\tif (!orgConf.enabled) return undefined;\n\t\tif (orgConf.key) return orgConf.key;\n\n\t\tconst { keyUrl, keyValidator } = this.config;\n\n\t\treturn this.context.getApiKey(\n\t\t\t{\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\trequiresAccount: this.descriptor.requiresAccount,\n\t\t\t\tvalidator: keyValidator != null ? v => keyValidator.test(v) : () => true,\n\t\t\t\turl: keyUrl,\n\t\t\t},\n\t\t\tsilent,\n\t\t);\n\t}\n\n\tabstract getModels(): Promise<readonly AIModel<T>[]>;\n\n\tprotected isResponseFormatSessionRejected(model: AIModel<T>, responseFormat: AIResponseFormat): boolean {\n\t\treturn isResponseFormatRejected(this.id, model, responseFormat);\n\t}\n\n\tprotected rememberResponseFormatRejection(model: AIModel<T>, responseFormat: AIResponseFormat): void {\n\t\trememberResponseFormatRejection(this.id, model, responseFormat);\n\t}\n\n\tprotected abstract getUrl(_model: AIModel<T>): string | undefined;\n\n\tprotected getHeaders<TAction extends AIActionType>(\n\t\t_action: TAction,\n\t\tapiKey: string,\n\t\t_model: AIModel<T>,\n\t\t_url: string,\n\t\t// Deliberately unused here: only the GitKraken provider forwards the conversation ID (as\n\t\t// `GK-Conversation-ID`); it must never reach third-party APIs.\n\t\t_conversationId?: string,\n\t): Record<string, string> | Promise<Record<string, string>> {\n\t\treturn {\n\t\t\tAccept: 'application/json',\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t};\n\t}\n\n\tasync sendRequest<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tgetMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\toptions: {\n\t\t\tsignal: AbortSignal;\n\t\t\tmodelOptions?: { outputTokens?: number; temperature?: number };\n\t\t\tconversationId?: string;\n\t\t\tresponseFormat?: AIResponseFormat;\n\t\t\ttools?: readonly AIToolDefinition[];\n\t\t},\n\t): Promise<AIProviderResponse<void> | undefined> {\n\t\tusing scope = maybeStartScopedLogger(`${getLoggableName(this)}.sendRequest`);\n\n\t\ttry {\n\t\t\tconst result = await this.fetch(\n\t\t\t\taction,\n\t\t\t\tmodel,\n\t\t\t\tapiKey,\n\t\t\t\tgetMessages,\n\t\t\t\toptions.modelOptions,\n\t\t\t\toptions.responseFormat,\n\t\t\t\toptions.signal,\n\t\t\t\toptions.conversationId,\n\t\t\t\toptions.tools,\n\t\t\t);\n\t\t\treturn result;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) {\n\t\t\t\tscope?.error(ex, `Cancelled request to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tscope?.error(ex, `Unable to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\tif (ex instanceof AIError) throw ex;\n\n\t\t\tdebugger;\n\t\t\tthrow new Error(getRequestErrorMessage(action, model.provider.name, ex.message), { cause: ex });\n\t\t}\n\t}\n\n\tprotected async fetch<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tmessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\tmodelOptions?: { outputTokens?: number; temperature?: number },\n\t\tresponseFormat?: AIResponseFormat,\n\t\tsignal?: AbortSignal,\n\t\tconversationId?: string,\n\t\ttools?: readonly AIToolDefinition[],\n\t): Promise<AIProviderResponse<void>> {\n\t\tlet retries = 0;\n\t\tlet maxInputTokens = model.maxTokens.input;\n\t\t// Latched when the provider rejects the `tools` field, so the retry drops tools entirely and\n\t\t// the caller can stop offering them for the rest of the session.\n\t\tlet toolsRejected = false;\n\n\t\twhile (true) {\n\t\t\tconst useTools = tools?.length && !toolsRejected ? tools : undefined;\n\t\t\tconst { messages: msgs, system } = this.extractSystemPrompt(await messages(maxInputTokens, retries));\n\t\t\tconst request: ChatCompletionRequest = {\n\t\t\t\tmodel: model.id,\n\t\t\t\tmessages: this.serializeMessages(msgs),\n\t\t\t\t...(system != null ? { system: system } : undefined),\n\t\t\t\t...(useTools != null ? this.serializeTools(useTools) : undefined),\n\t\t\t\tstream: false,\n\t\t\t\tmax_completion_tokens: model.maxTokens.output\n\t\t\t\t\t? Math.min(modelOptions?.outputTokens ?? Infinity, model.maxTokens.output)\n\t\t\t\t\t: modelOptions?.outputTokens,\n\t\t\t\ttemperature: getValidatedTemperature(\n\t\t\t\t\tmodel,\n\t\t\t\t\tmodelOptions?.temperature ?? model.temperature,\n\t\t\t\t\tthis.context.defaultTemperature,\n\t\t\t\t),\n\t\t\t};\n\t\t\tif (\n\t\t\t\tresponseFormat != null &&\n\t\t\t\t!this.isResponseFormatSessionRejected(model, responseFormat) &&\n\t\t\t\tthis.supportsResponseFormat(model)\n\t\t\t) {\n\t\t\t\tthis.applyResponseFormat(request, model, responseFormat);\n\t\t\t}\n\n\t\t\tlet rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t// Read once and forward to `handleFetchFailure` so the error body isn't decoded twice\n\t\t\tlet errorBody: string | undefined;\n\t\t\tif (!rsp.ok && (request.response_format != null || request.output_config != null)) {\n\t\t\t\t// If the native format was rejected (stale model snapshot, older Azure api-version,\n\t\t\t\t// proxy converter gaps, OpenRouter's require_parameters matching no endpoint), strip\n\t\t\t\t// it and resend the same request — the prompt still describes the JSON. Resending\n\t\t\t\t// directly avoids re-running the (non-idempotent) prompt build and its side effects\n\t\t\t\t// Leave it `undefined` (not '') when the read fails, so `handleFetchFailure` falls\n\t\t\t\t// back to reading the still-unconsumed response instead of parsing an empty string\n\t\t\t\terrorBody = await rsp\n\t\t\t\t\t.clone()\n\t\t\t\t\t.text()\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t\tif (this.isResponseFormatRejection(rsp.status, errorBody ?? '')) {\n\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t`(${this.name}) ${model.id}: native response format was rejected (${rsp.status}); retrying without it`,\n\t\t\t\t\t);\n\t\t\t\t\tconst rejected = responseFormat;\n\t\t\t\t\tresponseFormat = undefined;\n\t\t\t\t\tdelete request.response_format;\n\t\t\t\t\tdelete request.output_config;\n\t\t\t\t\tdelete request.provider;\n\t\t\t\t\trsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t\t\terrorBody = undefined;\n\t\t\t\t\t// Memoize only when stripping fixed it — proof the format (not e.g. an oversized\n\t\t\t\t\t// prompt behind the same wrapped status) caused the failure\n\t\t\t\t\tif (rsp.ok && rejected != null) {\n\t\t\t\t\t\tthis.rememberResponseFormatRejection(model, rejected);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!rsp.ok) {\n\t\t\t\tconst result = await this.handleFetchFailure(\n\t\t\t\t\trsp,\n\t\t\t\t\taction,\n\t\t\t\t\tmodel,\n\t\t\t\t\tretries,\n\t\t\t\t\tmaxInputTokens,\n\t\t\t\t\terrorBody,\n\t\t\t\t\tuseTools != null,\n\t\t\t\t);\n\t\t\t\tif (result.retry) {\n\t\t\t\t\tmaxInputTokens = result.maxInputTokens;\n\t\t\t\t\tif (result.withoutTools) {\n\t\t\t\t\t\t// Say so explicitly: the caller degrades silently by design, so the only remaining\n\t\t\t\t\t\t// evidence would be `toolCalls=0` — indistinguishable from a model that simply chose\n\t\t\t\t\t\t// not to consult the repository. That ambiguity is the whole reason to log it.\n\t\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t\t`(${this.name}) ${model.id}: tools were rejected (${rsp.status}); retrying without them — repository consultation is unavailable for the rest of this session`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttoolsRejected = true;\n\t\t\t\t\t}\n\t\t\t\t\tretries++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: ChatCompletionResponse = (await rsp.json()) as ChatCompletionResponse;\n\t\t\tconst toolCalls = this.parseToolCalls(data);\n\t\t\t// A tool-call-only turn carries no text, which `extractContent` correctly reports as '' — it\n\t\t\t// selects the text block by type, so a `tool_use` (or thinking) block is never mistaken for one.\n\t\t\t// The calls ride alongside on `toolCalls` below.\n\t\t\tconst content = this.extractContent(data);\n\t\t\t// Some providers/proxies (e.g. OpenRouter) report upstream failures as an OK response\n\t\t\t// carrying an error envelope and no choices — surface that instead of empty content\n\t\t\tif (!content && toolCalls == null && data.error != null) {\n\t\t\t\tif (data.error.code === 429 || data.error.code === '429') {\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${data.error.message || 'Rate limit exceeded'}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(`(${this.name}) ${data.error.message || 'Unknown error'}`);\n\t\t\t}\n\n\t\t\tconst result: AIProviderResponse<void> = {\n\t\t\t\tid: data.id ?? uuid(),\n\t\t\t\tcontent: content,\n\t\t\t\tfinishReason: this.extractFinishReason(data),\n\t\t\t\tmodel: model,\n\t\t\t\t...(toolCalls?.length ? { toolCalls: toolCalls } : undefined),\n\t\t\t\t...(toolsRejected ? { toolsRejected: true } : undefined),\n\t\t\t\tusage: {\n\t\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? data.usage?.input_tokens,\n\t\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? data.usage?.output_tokens,\n\t\t\t\t\ttotalTokens: data.usage?.total_tokens,\n\t\t\t\t\tlimits:\n\t\t\t\t\t\tdata?.usage?.gk != null\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tused: data.usage.gk.used,\n\t\t\t\t\t\t\t\t\tlimit: data.usage.gk.limit,\n\t\t\t\t\t\t\t\t\tresetsOn: new Date(data.usage.gk.resets_on),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tresult: undefined,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the message-related request fields. The default sends every message inline (OpenAI\n\t * Chat Completions semantics); providers whose API carries the initial system prompt separately\n\t * (e.g. Anthropic's top-level `system`) override this to extract it from the messages.\n\t */\n\tprotected extractSystemPrompt(messages: AIChatMessage<AIChatMessageRole>[]): {\n\t\tmessages: AIChatMessage<AIChatMessageRole>[];\n\t\tsystem?: string;\n\t} {\n\t\treturn { messages: messages };\n\t}\n\n\t/** Whether the given model accepts a native response-format/schema field; when false the\n\t * prompt's JSON description is the only format control (the request is unchanged).\n\t * Dynamic-catalog providers (GitKraken, OpenRouter) populate\n\t * {@link AIModel.supportsStructuredOutputs}; static providers override with id-based gates */\n\tprotected supportsResponseFormat(model: AIModel<T>): boolean {\n\t\treturn model.supportsStructuredOutputs ?? true;\n\t}\n\n\t/**\n\t * Whether a failed response reads as a rejection of the `tools` field rather than a problem with the\n\t * prompt — the trigger for one retry without tools, so a tool-using feature degrades to single-shot\n\t * instead of failing outright.\n\t *\n\t * Providers that wrap upstream rejections in their own envelope (e.g. GitKraken) must override this:\n\t * the wrapper's prose names neither \"tool\" nor \"function\", so the text match here cannot see through\n\t * it and the fallback would never fire.\n\t */\n\tprotected isToolsRejection(status: number, message: string | undefined): boolean {\n\t\tif (status !== 400 || !message) return false;\n\n\t\tconst m = message.toLowerCase();\n\t\treturn (\n\t\t\t(m.includes('tool') || m.includes('function')) &&\n\t\t\t(m.includes('unsupported') ||\n\t\t\t\tm.includes('not supported') ||\n\t\t\t\tm.includes('unknown') ||\n\t\t\t\tm.includes('unrecognized') ||\n\t\t\t\tm.includes('invalid') ||\n\t\t\t\tm.includes('unexpected'))\n\t\t);\n\t}\n\n\t/** Whether a failed response means the native response format itself was rejected — the\n\t * trigger for stripping it and retrying via the prompt-described JSON. Providers that wrap\n\t * upstream rejections in their own envelope (e.g. GitKraken) extend this */\n\tprotected isResponseFormatRejection(status: number, body: string): boolean {\n\t\treturn (\n\t\t\t(status === 400 && formatRejectionRegex.test(body)) ||\n\t\t\t// OpenRouter answers 404 when `require_parameters` matches no serving endpoint\n\t\t\t(status === 404 && /no endpoints found/i.test(body))\n\t\t);\n\t}\n\n\t/** Translates the canonical response format onto the request; called only when\n\t * {@link supportsResponseFormat} allows it. Base emits the OpenAI `response_format.json_schema`\n\t * envelope. Overrides must REPLACE this for providers with a different native field\n\t * (e.g. Anthropic `output_config`) — never leave both on the request. */\n\tprotected applyResponseFormat(\n\t\trequest: ChatCompletionRequest,\n\t\t_model: AIModel<T>,\n\t\tresponseFormat: AIResponseFormat,\n\t): void {\n\t\trequest.response_format = {\n\t\t\ttype: 'json_schema',\n\t\t\tjson_schema: {\n\t\t\t\tname: responseFormat.name,\n\t\t\t\tstrict: true,\n\t\t\t\tschema: responseFormat.schema,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Finds the text block by type rather than position — with some Anthropic models (e.g. Sonnet 5,\n\t// where thinking runs by default) a thinking block can precede the text block\n\tprotected extractContent(data: ChatCompletionResponse): string {\n\t\treturn (\n\t\t\tdata.choices?.[0]?.message?.content?.trim() ??\n\t\t\tdata.content?.find(b => b.type === 'text')?.text?.trim() ??\n\t\t\t''\n\t\t);\n\t}\n\n\t/** Normalizes abnormal completion signals (all arrive as HTTP 200) so consumers can\n\t * distinguish truncated/filtered/refused output from a malformed-but-complete reply */\n\tprotected extractFinishReason(data: ChatCompletionResponse): AIFinishReason | undefined {\n\t\tconst choice = data.choices?.[0];\n\t\tif (choice != null) {\n\t\t\t// Truthy check — some OpenAI-compatible servers serialize `\"refusal\": \"\"` on normal replies\n\t\t\tif (choice.message?.refusal) return 'refusal';\n\t\t\tif (choice.finish_reason === 'length') return 'length';\n\t\t\tif (choice.finish_reason === 'content_filter') return 'content_filter';\n\t\t\treturn undefined;\n\t\t}\n\n\t\tswitch (data.stop_reason) {\n\t\t\t// `model_context_window_exceeded` means the context filled before the output limit — the\n\t\t\t// reply is truncated all the same\n\t\t\tcase 'max_tokens':\n\t\t\tcase 'model_context_window_exceeded':\n\t\t\t\treturn 'length';\n\t\t\tcase 'refusal':\n\t\t\t\treturn 'refusal';\n\t\t\tdefault:\n\t\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Maps messages onto the provider's wire shape. The default is OpenAI Chat Completions: assistant\n\t * tool calls go in `tool_calls` with `arguments` JSON-stringified, and tool results are `tool`-role\n\t * entries keyed by `tool_call_id`. Providers with a different tool encoding (e.g. Anthropic's\n\t * content blocks) override this.\n\t */\n\tprotected serializeMessages(messages: AIChatMessage<AIChatMessageRole>[]): unknown[] {\n\t\treturn messages.map(m => {\n\t\t\tif (m.role === 'tool') {\n\t\t\t\t// A tool result is only addressable by the id of the call it answers. Without one, send it\n\t\t\t\t// as plain text — the same shape used for providers that can't carry tool calls at all —\n\t\t\t\t// rather than a `tool_call_id: undefined` the provider rejects with nothing to trace it to.\n\t\t\t\tif (m.toolCallId == null) return { role: 'user', content: m.content };\n\n\t\t\t\treturn { role: 'tool', tool_call_id: m.toolCallId, content: m.content };\n\t\t\t}\n\n\t\t\tif (m.role === 'assistant' && m.toolCalls?.length) {\n\t\t\t\treturn {\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\t// OpenAI requires `content` to be present, but allows null on a tool-call-only turn\n\t\t\t\t\tcontent: m.content || null,\n\t\t\t\t\ttool_calls: m.toolCalls.map(c => ({\n\t\t\t\t\t\tid: c.id,\n\t\t\t\t\t\ttype: 'function',\n\t\t\t\t\t\tfunction: { name: c.name, arguments: JSON.stringify(c.args) },\n\t\t\t\t\t\t// Only the GitKraken proxy mints these, so the field can only ever be present on a\n\t\t\t\t\t\t// conversation with that proxy — no other provider sees it.\n\t\t\t\t\t\t...(c.providerSignature != null ? { thought_signature: c.providerSignature } : undefined),\n\t\t\t\t\t})),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn { role: m.role, content: m.content };\n\t\t});\n\t}\n\n\t/** Returns the tool-related request fields. The default is the OpenAI `function` envelope. */\n\tprotected serializeTools(tools: readonly AIToolDefinition[]): { tools: unknown[] } {\n\t\treturn {\n\t\t\ttools: tools.map(t => ({\n\t\t\t\ttype: 'function',\n\t\t\t\tfunction: { name: t.name, description: t.description, parameters: t.parameters },\n\t\t\t})),\n\t\t};\n\t}\n\n\t/**\n\t * Extracts tool calls from a response, handling both the OpenAI shape\n\t * (`choices[].message.tool_calls`, whose `arguments` is a JSON string) and the Anthropic shape\n\t * (`content[]` blocks of type `tool_use`) — mirroring how {@link fetch} already reads content\n\t * from both.\n\t */\n\tprotected parseToolCalls(data: ChatCompletionResponse): AIToolCall[] | undefined {\n\t\tconst calls: AIToolCall[] = [];\n\n\t\tfor (const call of data.choices?.[0]?.message?.tool_calls ?? []) {\n\t\t\tlet args: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\t// Providers can emit '' for a no-argument call\n\t\t\t\targs = call.function.arguments ? (JSON.parse(call.function.arguments) as Record<string, unknown>) : {};\n\t\t\t} catch {\n\t\t\t\t// Leave args empty — the tool dispatcher reports the resulting validation error back to\n\t\t\t\t// the model, which is more recoverable than failing the whole request here.\n\t\t\t}\n\n\t\t\tcalls.push({\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.function.name,\n\t\t\t\targs: args,\n\t\t\t\t...(call.thought_signature != null ? { providerSignature: call.thought_signature } : undefined),\n\t\t\t});\n\t\t}\n\n\t\tfor (const block of data.content ?? []) {\n\t\t\tif (block.type !== 'tool_use' || block.id == null || block.name == null) continue;\n\n\t\t\tcalls.push({ id: block.id, name: block.name, args: block.input ?? {} });\n\t\t}\n\n\t\treturn calls.length ? calls : undefined;\n\t}\n\n\tprotected async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\t_action: TAction,\n\t\tmodel: AIModel<T>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\t/** The already-read error body, when the caller had to read it (avoids a second decode) */\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\tif (rsp.status === 404) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`Your API key doesn't seem to have access to the selected '${model.id}' model`),\n\t\t\t);\n\t\t}\n\t\tif (rsp.status === 429) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\tnew Error(\n\t\t\t\t\t`(${this.name}) ${rsp.status}: Too many requests (rate limit exceeded) or your account is out of funds`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as\n\t\t\t\t| { error?: { code: string; message: string } }\n\t\t\t\t| undefined;\n\t\t} catch {}\n\n\t\tif (Array.isArray(json)) {\n\t\t\tjson = json[0];\n\t\t}\n\t\tif (json?.error?.code === 'context_length_exceeded') {\n\t\t\tif (retries < 3) {\n\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t}\n\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\t// A provider that advertises the OpenAI shape but rejects `tools` (an older or partial\n\t\t// implementation) gets one retry without them, so the caller degrades to text-only instead of\n\t\t// failing outright.\n\t\tif (sentTools && this.isToolsRejection(rsp.status, json?.error?.message)) {\n\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t}\n\n\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`);\n\t}\n\n\tprotected async fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t\tconversationId?: string,\n\t): Promise<Response> {\n\t\tconst url = this.getUrl(model);\n\t\tif (!url) {\n\t\t\tthrow new Error(`(${this.name}) ${getActionName(action)}: No URL configured`);\n\t\t}\n\n\t\tconst body = JSON.stringify(request);\n\n\t\t// A rejected request is otherwise undiagnosable: a proxying backend reports only its own\n\t\t// \"upstream AI provider error (<provider> HTTP <status>)\", so which field the upstream objected to\n\t\t// can only be inferred. Log the request's shape — which fields were sent, and how much — but not\n\t\t// the messages, which carry the user's source code.\n\t\tLogger.trace(\n\t\t\t`(${this.name}) ${getActionName(action)} → ${model.id}: fields=[${Object.keys(request)\n\t\t\t\t.sort()\n\t\t\t\t.join(',')}] bytes=${body.length}`,\n\t\t);\n\n\t\ttry {\n\t\t\treturn await this.context.fetch(url, {\n\t\t\t\theaders: await this.getHeaders(action, apiKey, model, url, conversationId),\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: body,\n\t\t\t\tsignal: signal,\n\t\t\t});\n\t\t} catch (ex) {\n\t\t\tif (ex.name === 'AbortError') throw new CancellationError(ex);\n\t\t\tthrow ex;\n\t\t}\n\t}\n}\n\nexport interface ChatCompletionRequest {\n\tmodel: string;\n\t/** Provider-shaped messages from {@link OpenAICompatibleProviderBase.serializeMessages} */\n\tmessages: unknown[];\n\n\t/** Anthropic carries the initial system prompt here instead of as a `system`-role message */\n\tsystem?: string;\n\n\t/** Tool definitions, shaped by {@link OpenAICompatibleProviderBase.serializeTools} */\n\ttools?: unknown[];\n\n\t/** @deprecated but used by Anthropic & Gemini */\n\tmax_tokens?: number;\n\t/** Currently can't be used for Anthropic & Gemini */\n\tmax_completion_tokens?: number;\n\tmetadata?: Record<string, string>;\n\tstream?: boolean;\n\ttemperature?: number;\n\ttop_p?: number;\n\n\t/** Not supported by many models/providers */\n\treasoning_effort?: 'low' | 'medium' | 'high';\n\n\t/** OpenAI-compatible structured output (set via {@link OpenAICompatibleProviderBase.applyResponseFormat}) */\n\tresponse_format?: { type: 'json_schema'; json_schema: { name: string; strict: boolean; schema: JSONSchema } };\n\t/** Anthropic-native structured output (set only by the Anthropic override) */\n\toutput_config?: { format: { type: 'json_schema'; schema: JSONSchema } };\n\t/** OpenRouter-only routing constraint (set only by the OpenRouter override) */\n\tprovider?: { require_parameters: boolean };\n}\n\nexport interface ChatCompletionResponse {\n\tid: string;\n\tmodel: string;\n\t/** OpenAI compatible output */\n\tchoices?: {\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: string;\n\t\t\tcontent: string | null;\n\t\t\t/** OpenAI-only; most OpenAI-compatible servers omit it entirely */\n\t\t\trefusal?: string | null;\n\t\t\ttool_calls?: {\n\t\t\t\tid: string;\n\t\t\t\ttype: string;\n\t\t\t\tfunction: { name: string; arguments: string };\n\t\t\t\t/** GitKraken proxy only — an opaque provider reasoning token to round-trip */\n\t\t\t\tthought_signature?: string;\n\t\t\t}[];\n\t\t};\n\t\tfinish_reason: string;\n\t}[];\n\t/** Anthropic output (non-text blocks, e.g. thinking, carry no text). `tool_use` blocks add the\n\t * call fields, which is why `id`/`name` are optional here — {@link parseToolCalls} narrows on\n\t * `type` and then checks them. */\n\tcontent?: { type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }[];\n\t/** Error envelope some providers/proxies return with an OK status (e.g. OpenRouter upstream failures) */\n\terror?: { message?: string; code?: number | string } | null;\n\t/** Anthropic stop reason */\n\tstop_reason?: string;\n\tusage: {\n\t\t/** OpenAI compatible */\n\t\tprompt_tokens?: number;\n\t\tcompletion_tokens?: number;\n\t\ttotal_tokens?: number;\n\n\t\t/** Anthropic */\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\n\t\t/** GitKraken */\n\t\tgk: {\n\t\t\tused: number;\n\t\t\tlimit: number;\n\t\t\tresets_on: string;\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"openAICompatibleProviderBase.js","names":[],"sources":["../../../../../plus/ai/src/providers/openAICompatibleProviderBase.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\nimport { CancellationError, isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uuid } from '@gitlens/utils/crypto.js';\nimport { getLoggableName, Logger } from '@gitlens/utils/logger.js';\nimport { maybeStartScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { AIProviders } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel, AIProviderDescriptor } from '../models/model.js';\nimport type {\n\tAIChatMessage,\n\tAIChatMessageRole,\n\tAIFinishReason,\n\tAIProvider,\n\tAIProviderResponse,\n\tAIResponseFormat,\n\tAIToolCall,\n\tAIToolDefinition,\n\tJSONSchema,\n} from '../models/provider.js';\nimport { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from '../utils/ai.utils.js';\nimport type { AIProviderContext } from './context.js';\nimport { isResponseFormatRejected, rememberResponseFormatRejection } from './responseFormatCache.js';\n\n// Native-format rejection wording varies by provider (\"response_format\", \"structured outputs\",\n// Gemini's \"Json mode is not enabled\", and its OpenAI-compat layer's `generation_config.response_schema`\n// path in INVALID_ARGUMENT bodies, ...)\nconst formatRejectionRegex =\n\t/response[_ ]?(?:format|schema)|structured[_ ]?output|output_config|json_schema|json[_ ]?mode/i;\n\nfunction getRequestErrorMessage(action: AIActionType, providerName: string, message: string): string {\n\tconst args = { provider: providerName, message: message };\n\tswitch (action) {\n\t\tcase 'explain-changes':\n\t\t\treturn l10n.t('Unable to Explain Changes: ({provider}) {message}', args);\n\t\tcase 'review-changes':\n\t\t\treturn l10n.t('Unable to Review Changes: ({provider}) {message}', args);\n\t\tcase 'generate-commitMessage':\n\t\t\treturn l10n.t('Unable to Generate Commit Message: ({provider}) {message}', args);\n\t\tcase 'generate-stashMessage':\n\t\t\treturn l10n.t('Unable to Generate Stash Message: ({provider}) {message}', args);\n\t\tcase 'generate-changelog':\n\t\t\treturn l10n.t('Unable to Generate Changelog: ({provider}) {message}', args);\n\t\tcase 'generate-create-cloudPatch':\n\t\t\treturn l10n.t('Unable to Create Cloud Patch Details: ({provider}) {message}', args);\n\t\tcase 'generate-create-pullRequest':\n\t\t\treturn l10n.t('Unable to Create Pull Request Details: ({provider}) {message}', args);\n\t\tcase 'generate-commits':\n\t\t\treturn l10n.t('Unable to Generate Commits: ({provider}) {message}', args);\n\t\tcase 'conflict-resolution':\n\t\t\treturn l10n.t('Unable to Resolve Conflicts (Preview): ({provider}) {message}', args);\n\t\tcase 'generate-searchQuery':\n\t\t\treturn l10n.t('Unable to Generate Search Query: ({provider}) {message}', args);\n\t}\n}\n\nexport abstract class OpenAICompatibleProviderBase<T extends AIProviders> implements AIProvider<T> {\n\tconstructor(protected readonly context: AIProviderContext) {}\n\n\tdispose(): void {}\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tabstract readonly id: T;\n\tabstract readonly name: string;\n\tprotected abstract readonly descriptor: AIProviderDescriptor<T>;\n\tprotected abstract readonly config: { keyUrl?: string; keyValidator?: RegExp };\n\n\tasync configured(silent: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst apiKey = await this.getApiKey(silent);\n\t\t\treturn apiKey != null;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) return false;\n\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\tasync getApiKey(silent: boolean): Promise<string | undefined> {\n\t\tconst orgConf = this.context.getProviderConfig(this.id);\n\t\tif (!orgConf.enabled) return undefined;\n\t\tif (orgConf.key) return orgConf.key;\n\n\t\tconst { keyUrl, keyValidator } = this.config;\n\n\t\treturn this.context.getApiKey(\n\t\t\t{\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\trequiresAccount: this.descriptor.requiresAccount,\n\t\t\t\tvalidator: keyValidator != null ? v => keyValidator.test(v) : () => true,\n\t\t\t\turl: keyUrl,\n\t\t\t},\n\t\t\tsilent,\n\t\t);\n\t}\n\n\tabstract getModels(): Promise<readonly AIModel<T>[]>;\n\n\tprotected isResponseFormatSessionRejected(model: AIModel<T>, responseFormat: AIResponseFormat): boolean {\n\t\treturn isResponseFormatRejected(this.id, model, responseFormat);\n\t}\n\n\tprotected rememberResponseFormatRejection(model: AIModel<T>, responseFormat: AIResponseFormat): void {\n\t\trememberResponseFormatRejection(this.id, model, responseFormat);\n\t}\n\n\tprotected abstract getUrl(_model: AIModel<T>): string | undefined;\n\n\tprotected getHeaders<TAction extends AIActionType>(\n\t\t_action: TAction,\n\t\tapiKey: string,\n\t\t_model: AIModel<T>,\n\t\t_url: string,\n\t\t// Deliberately unused here: only the GitKraken provider forwards the conversation ID (as\n\t\t// `GK-Conversation-ID`); it must never reach third-party APIs.\n\t\t_conversationId?: string,\n\t): Record<string, string> | Promise<Record<string, string>> {\n\t\treturn {\n\t\t\tAccept: 'application/json',\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t};\n\t}\n\n\tasync sendRequest<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tgetMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\toptions: {\n\t\t\tsignal: AbortSignal;\n\t\t\tmodelOptions?: { outputTokens?: number; temperature?: number };\n\t\t\tconversationId?: string;\n\t\t\tresponseFormat?: AIResponseFormat;\n\t\t\ttools?: readonly AIToolDefinition[];\n\t\t},\n\t): Promise<AIProviderResponse<void> | undefined> {\n\t\tusing scope = maybeStartScopedLogger(`${getLoggableName(this)}.sendRequest`);\n\n\t\ttry {\n\t\t\tconst result = await this.fetch(\n\t\t\t\taction,\n\t\t\t\tmodel,\n\t\t\t\tapiKey,\n\t\t\t\tgetMessages,\n\t\t\t\toptions.modelOptions,\n\t\t\t\toptions.responseFormat,\n\t\t\t\toptions.signal,\n\t\t\t\toptions.conversationId,\n\t\t\t\toptions.tools,\n\t\t\t);\n\t\t\treturn result;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) {\n\t\t\t\tscope?.error(ex, `Cancelled request to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tscope?.error(ex, `Unable to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\tif (ex instanceof AIError) throw ex;\n\n\t\t\tdebugger;\n\t\t\tthrow new Error(getRequestErrorMessage(action, model.provider.name, ex.message), { cause: ex });\n\t\t}\n\t}\n\n\tprotected async fetch<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tmessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\tmodelOptions?: { outputTokens?: number; temperature?: number },\n\t\tresponseFormat?: AIResponseFormat,\n\t\tsignal?: AbortSignal,\n\t\tconversationId?: string,\n\t\ttools?: readonly AIToolDefinition[],\n\t): Promise<AIProviderResponse<void>> {\n\t\tlet retries = 0;\n\t\tlet maxInputTokens = model.maxTokens.input;\n\t\t// Latched when the provider rejects the `tools` field, so the retry drops tools entirely and\n\t\t// the caller can stop offering them for the rest of the session.\n\t\tlet toolsRejected = false;\n\n\t\twhile (true) {\n\t\t\tconst useTools = tools?.length && !toolsRejected ? tools : undefined;\n\t\t\tconst { messages: msgs, system } = this.extractSystemPrompt(await messages(maxInputTokens, retries));\n\t\t\tconst request: ChatCompletionRequest = {\n\t\t\t\tmodel: model.id,\n\t\t\t\tmessages: this.serializeMessages(msgs),\n\t\t\t\t...(system != null ? { system: system } : undefined),\n\t\t\t\t...(useTools != null ? this.serializeTools(useTools) : undefined),\n\t\t\t\tstream: false,\n\t\t\t\tmax_completion_tokens: model.maxTokens.output\n\t\t\t\t\t? Math.min(modelOptions?.outputTokens ?? Infinity, model.maxTokens.output)\n\t\t\t\t\t: modelOptions?.outputTokens,\n\t\t\t\ttemperature: getValidatedTemperature(\n\t\t\t\t\tmodel,\n\t\t\t\t\tmodelOptions?.temperature ?? model.temperature,\n\t\t\t\t\tthis.context.defaultTemperature,\n\t\t\t\t),\n\t\t\t};\n\t\t\tif (\n\t\t\t\tresponseFormat != null &&\n\t\t\t\t!this.isResponseFormatSessionRejected(model, responseFormat) &&\n\t\t\t\tthis.supportsResponseFormat(model)\n\t\t\t) {\n\t\t\t\tthis.applyResponseFormat(request, model, responseFormat);\n\t\t\t}\n\n\t\t\tlet rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t// Read once and forward to `handleFetchFailure` so the error body isn't decoded twice\n\t\t\tlet errorBody: string | undefined;\n\t\t\tif (!rsp.ok && (request.response_format != null || request.output_config != null)) {\n\t\t\t\t// If the native format was rejected (stale model snapshot, older Azure api-version,\n\t\t\t\t// proxy converter gaps, OpenRouter's require_parameters matching no endpoint), strip\n\t\t\t\t// it and resend the same request — the prompt still describes the JSON. Resending\n\t\t\t\t// directly avoids re-running the (non-idempotent) prompt build and its side effects\n\t\t\t\t// Leave it `undefined` (not '') when the read fails, so `handleFetchFailure` falls\n\t\t\t\t// back to reading the still-unconsumed response instead of parsing an empty string\n\t\t\t\terrorBody = await rsp\n\t\t\t\t\t.clone()\n\t\t\t\t\t.text()\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t\tif (this.isResponseFormatRejection(rsp.status, errorBody ?? '')) {\n\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t`(${this.name}) ${model.id}: native response format was rejected (${rsp.status}); retrying without it`,\n\t\t\t\t\t);\n\t\t\t\t\tconst rejected = responseFormat;\n\t\t\t\t\tresponseFormat = undefined;\n\t\t\t\t\tdelete request.response_format;\n\t\t\t\t\tdelete request.output_config;\n\t\t\t\t\tdelete request.provider;\n\t\t\t\t\trsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t\t\terrorBody = undefined;\n\t\t\t\t\t// Memoize only when stripping fixed it — proof the format (not e.g. an oversized\n\t\t\t\t\t// prompt behind the same wrapped status) caused the failure\n\t\t\t\t\tif (rsp.ok && rejected != null) {\n\t\t\t\t\t\tthis.rememberResponseFormatRejection(model, rejected);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!rsp.ok) {\n\t\t\t\tconst result = await this.handleFetchFailure(\n\t\t\t\t\trsp,\n\t\t\t\t\taction,\n\t\t\t\t\tmodel,\n\t\t\t\t\tretries,\n\t\t\t\t\tmaxInputTokens,\n\t\t\t\t\terrorBody,\n\t\t\t\t\tuseTools != null,\n\t\t\t\t);\n\t\t\t\tif (result.retry) {\n\t\t\t\t\tmaxInputTokens = result.maxInputTokens;\n\t\t\t\t\tif (result.withoutTools) {\n\t\t\t\t\t\t// Say so explicitly: the caller degrades silently by design, so the only remaining\n\t\t\t\t\t\t// evidence would be `toolCalls=0` — indistinguishable from a model that simply chose\n\t\t\t\t\t\t// not to consult the repository. That ambiguity is the whole reason to log it.\n\t\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t\t`(${this.name}) ${model.id}: tools were rejected (${rsp.status}); retrying without them — repository consultation is unavailable for the rest of this session`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttoolsRejected = true;\n\t\t\t\t\t}\n\t\t\t\t\tretries++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: ChatCompletionResponse = (await rsp.json()) as ChatCompletionResponse;\n\t\t\tconst toolCalls = this.parseToolCalls(data);\n\t\t\t// A tool-call-only turn carries no text, which `extractContent` correctly reports as '' — it\n\t\t\t// selects the text block by type, so a `tool_use` (or thinking) block is never mistaken for one.\n\t\t\t// The calls ride alongside on `toolCalls` below.\n\t\t\tconst content = this.extractContent(data);\n\t\t\t// Some providers/proxies (e.g. OpenRouter) report upstream failures as an OK response\n\t\t\t// carrying an error envelope and no choices — surface that instead of empty content\n\t\t\tif (!content && toolCalls == null && data.error != null) {\n\t\t\t\tif (data.error.code === 429 || data.error.code === '429') {\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${data.error.message || 'Rate limit exceeded'}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(`(${this.name}) ${data.error.message || 'Unknown error'}`);\n\t\t\t}\n\n\t\t\tconst result: AIProviderResponse<void> = {\n\t\t\t\tid: data.id ?? uuid(),\n\t\t\t\tcontent: content,\n\t\t\t\tfinishReason: this.extractFinishReason(data),\n\t\t\t\tmodel: model,\n\t\t\t\t...(toolCalls?.length ? { toolCalls: toolCalls } : undefined),\n\t\t\t\t...(toolsRejected ? { toolsRejected: true } : undefined),\n\t\t\t\tusage: {\n\t\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? data.usage?.input_tokens,\n\t\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? data.usage?.output_tokens,\n\t\t\t\t\ttotalTokens: data.usage?.total_tokens,\n\t\t\t\t\tlimits:\n\t\t\t\t\t\tdata?.usage?.gk != null\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tused: data.usage.gk.used,\n\t\t\t\t\t\t\t\t\tlimit: data.usage.gk.limit,\n\t\t\t\t\t\t\t\t\tresetsOn: new Date(data.usage.gk.resets_on),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tresult: undefined,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the message-related request fields. The default sends every message inline (OpenAI\n\t * Chat Completions semantics); providers whose API carries the initial system prompt separately\n\t * (e.g. Anthropic's top-level `system`) override this to extract it from the messages.\n\t */\n\tprotected extractSystemPrompt(messages: AIChatMessage<AIChatMessageRole>[]): {\n\t\tmessages: AIChatMessage<AIChatMessageRole>[];\n\t\tsystem?: string;\n\t} {\n\t\treturn { messages: messages };\n\t}\n\n\t/** Whether the given model accepts a native response-format/schema field; when false the\n\t * prompt's JSON description is the only format control (the request is unchanged).\n\t * Dynamic-catalog providers (GitKraken, OpenRouter) populate\n\t * {@link AIModel.supportsStructuredOutputs}; static providers override with id-based gates */\n\tprotected supportsResponseFormat(model: AIModel<T>): boolean {\n\t\treturn model.supportsStructuredOutputs ?? true;\n\t}\n\n\t/**\n\t * Whether a failed response reads as a rejection of the `tools` field rather than a problem with the\n\t * prompt — the trigger for one retry without tools, so a tool-using feature degrades to single-shot\n\t * instead of failing outright.\n\t *\n\t * Providers that wrap upstream rejections in their own envelope (e.g. GitKraken) must override this:\n\t * the wrapper's prose names neither \"tool\" nor \"function\", so the text match here cannot see through\n\t * it and the fallback would never fire.\n\t */\n\tprotected isToolsRejection(status: number, message: string | undefined): boolean {\n\t\tif (status !== 400 || !message) return false;\n\n\t\tconst m = message.toLowerCase();\n\t\treturn (\n\t\t\t(m.includes('tool') || m.includes('function')) &&\n\t\t\t(m.includes('unsupported') ||\n\t\t\t\tm.includes('not supported') ||\n\t\t\t\tm.includes('unknown') ||\n\t\t\t\tm.includes('unrecognized') ||\n\t\t\t\tm.includes('invalid') ||\n\t\t\t\tm.includes('unexpected'))\n\t\t);\n\t}\n\n\t/** Whether a failed response means the native response format itself was rejected — the\n\t * trigger for stripping it and retrying via the prompt-described JSON. Providers that wrap\n\t * upstream rejections in their own envelope (e.g. GitKraken) extend this */\n\tprotected isResponseFormatRejection(status: number, body: string): boolean {\n\t\treturn (\n\t\t\t(status === 400 && formatRejectionRegex.test(body)) ||\n\t\t\t// OpenRouter answers 404 when `require_parameters` matches no serving endpoint\n\t\t\t(status === 404 && /no endpoints found/i.test(body))\n\t\t);\n\t}\n\n\t/** Translates the canonical response format onto the request; called only when\n\t * {@link supportsResponseFormat} allows it. Base emits the OpenAI `response_format.json_schema`\n\t * envelope. Overrides must REPLACE this for providers with a different native field\n\t * (e.g. Anthropic `output_config`) — never leave both on the request. */\n\tprotected applyResponseFormat(\n\t\trequest: ChatCompletionRequest,\n\t\t_model: AIModel<T>,\n\t\tresponseFormat: AIResponseFormat,\n\t): void {\n\t\trequest.response_format = {\n\t\t\ttype: 'json_schema',\n\t\t\tjson_schema: {\n\t\t\t\tname: responseFormat.name,\n\t\t\t\tstrict: true,\n\t\t\t\tschema: responseFormat.schema,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Finds the text block by type rather than position — with some Anthropic models (e.g. Sonnet 5,\n\t// where thinking runs by default) a thinking block can precede the text block\n\tprotected extractContent(data: ChatCompletionResponse): string {\n\t\treturn (\n\t\t\tdata.choices?.[0]?.message?.content?.trim() ??\n\t\t\tdata.content?.find(b => b.type === 'text')?.text?.trim() ??\n\t\t\t''\n\t\t);\n\t}\n\n\t/** Normalizes abnormal completion signals (all arrive as HTTP 200) so consumers can\n\t * distinguish truncated/filtered/refused output from a malformed-but-complete reply */\n\tprotected extractFinishReason(data: ChatCompletionResponse): AIFinishReason | undefined {\n\t\tconst choice = data.choices?.[0];\n\t\tif (choice != null) {\n\t\t\t// Truthy check — some OpenAI-compatible servers serialize `\"refusal\": \"\"` on normal replies\n\t\t\tif (choice.message?.refusal) return 'refusal';\n\t\t\tif (choice.finish_reason === 'length') return 'length';\n\t\t\tif (choice.finish_reason === 'content_filter') return 'content_filter';\n\t\t\treturn undefined;\n\t\t}\n\n\t\tswitch (data.stop_reason) {\n\t\t\t// `model_context_window_exceeded` means the context filled before the output limit — the\n\t\t\t// reply is truncated all the same\n\t\t\tcase 'max_tokens':\n\t\t\tcase 'model_context_window_exceeded':\n\t\t\t\treturn 'length';\n\t\t\tcase 'refusal':\n\t\t\t\treturn 'refusal';\n\t\t\tdefault:\n\t\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Maps messages onto the provider's wire shape. The default is OpenAI Chat Completions: assistant\n\t * tool calls go in `tool_calls` with `arguments` JSON-stringified, and tool results are `tool`-role\n\t * entries keyed by `tool_call_id`. Providers with a different tool encoding (e.g. Anthropic's\n\t * content blocks) override this.\n\t */\n\tprotected serializeMessages(messages: AIChatMessage<AIChatMessageRole>[]): unknown[] {\n\t\treturn messages.map(m => {\n\t\t\tif (m.role === 'tool') {\n\t\t\t\t// A tool result is only addressable by the id of the call it answers. Without one, send it\n\t\t\t\t// as plain text — the same shape used for providers that can't carry tool calls at all —\n\t\t\t\t// rather than a `tool_call_id: undefined` the provider rejects with nothing to trace it to.\n\t\t\t\tif (m.toolCallId == null) return { role: 'user', content: m.content };\n\n\t\t\t\treturn { role: 'tool', tool_call_id: m.toolCallId, content: m.content };\n\t\t\t}\n\n\t\t\tif (m.role === 'assistant' && m.toolCalls?.length) {\n\t\t\t\treturn {\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\t// OpenAI requires `content` to be present, but allows null on a tool-call-only turn\n\t\t\t\t\tcontent: m.content || null,\n\t\t\t\t\ttool_calls: m.toolCalls.map(c => ({\n\t\t\t\t\t\tid: c.id,\n\t\t\t\t\t\ttype: 'function',\n\t\t\t\t\t\tfunction: { name: c.name, arguments: JSON.stringify(c.args) },\n\t\t\t\t\t\t// Only the GitKraken proxy mints these, so the field can only ever be present on a\n\t\t\t\t\t\t// conversation with that proxy — no other provider sees it.\n\t\t\t\t\t\t...(c.providerSignature != null ? { thought_signature: c.providerSignature } : undefined),\n\t\t\t\t\t})),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn { role: m.role, content: m.content };\n\t\t});\n\t}\n\n\t/** Returns the tool-related request fields. The default is the OpenAI `function` envelope. */\n\tprotected serializeTools(tools: readonly AIToolDefinition[]): { tools: unknown[] } {\n\t\treturn {\n\t\t\ttools: tools.map(t => ({\n\t\t\t\ttype: 'function',\n\t\t\t\tfunction: { name: t.name, description: t.description, parameters: t.parameters },\n\t\t\t})),\n\t\t};\n\t}\n\n\t/**\n\t * Extracts tool calls from a response, handling both the OpenAI shape\n\t * (`choices[].message.tool_calls`, whose `arguments` is a JSON string) and the Anthropic shape\n\t * (`content[]` blocks of type `tool_use`) — mirroring how {@link fetch} already reads content\n\t * from both.\n\t */\n\tprotected parseToolCalls(data: ChatCompletionResponse): AIToolCall[] | undefined {\n\t\tconst calls: AIToolCall[] = [];\n\n\t\tfor (const call of data.choices?.[0]?.message?.tool_calls ?? []) {\n\t\t\tlet args: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\t// Providers can emit '' for a no-argument call\n\t\t\t\targs = call.function.arguments ? (JSON.parse(call.function.arguments) as Record<string, unknown>) : {};\n\t\t\t} catch {\n\t\t\t\t// Leave args empty — the tool dispatcher reports the resulting validation error back to\n\t\t\t\t// the model, which is more recoverable than failing the whole request here.\n\t\t\t}\n\n\t\t\tcalls.push({\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.function.name,\n\t\t\t\targs: args,\n\t\t\t\t...(call.thought_signature != null ? { providerSignature: call.thought_signature } : undefined),\n\t\t\t});\n\t\t}\n\n\t\tfor (const block of data.content ?? []) {\n\t\t\tif (block.type !== 'tool_use' || block.id == null || block.name == null) continue;\n\n\t\t\tcalls.push({ id: block.id, name: block.name, args: block.input ?? {} });\n\t\t}\n\n\t\treturn calls.length ? calls : undefined;\n\t}\n\n\tprotected async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\t_action: TAction,\n\t\tmodel: AIModel<T>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\t/** The already-read error body, when the caller had to read it (avoids a second decode) */\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\tif (rsp.status === 404) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`Your API key doesn't seem to have access to the selected '${model.id}' model`),\n\t\t\t);\n\t\t}\n\t\tif (rsp.status === 429) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\tnew Error(\n\t\t\t\t\t`(${this.name}) ${rsp.status}: Too many requests (rate limit exceeded) or your account is out of funds`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as\n\t\t\t\t| { error?: { code: string; message: string } }\n\t\t\t\t| undefined;\n\t\t} catch {}\n\n\t\tif (Array.isArray(json)) {\n\t\t\tjson = json[0];\n\t\t}\n\n\t\t// A revoked/invalid key or no access: AI is unavailable to this user, not a one-off failure.\n\t\t// Neither retry probe below can apply — both only match a 400 (or the OpenRouter 404 handled above)\n\t\tif (rsp.status === 401 || rsp.status === 403) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\tif (json?.error?.code === 'context_length_exceeded') {\n\t\t\tif (retries < 3) {\n\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t}\n\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\t// A provider that advertises the OpenAI shape but rejects `tools` (an older or partial\n\t\t// implementation) gets one retry without them, so the caller degrades to text-only instead of\n\t\t// failing outright.\n\t\tif (sentTools && this.isToolsRejection(rsp.status, json?.error?.message)) {\n\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t}\n\n\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`);\n\t}\n\n\tprotected async fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t\tconversationId?: string,\n\t): Promise<Response> {\n\t\tconst url = this.getUrl(model);\n\t\tif (!url) {\n\t\t\tthrow new Error(`(${this.name}) ${getActionName(action)}: No URL configured`);\n\t\t}\n\n\t\tconst body = JSON.stringify(request);\n\n\t\t// A rejected request is otherwise undiagnosable: a proxying backend reports only its own\n\t\t// \"upstream AI provider error (<provider> HTTP <status>)\", so which field the upstream objected to\n\t\t// can only be inferred. Log the request's shape — which fields were sent, and how much — but not\n\t\t// the messages, which carry the user's source code.\n\t\tLogger.trace(\n\t\t\t`(${this.name}) ${getActionName(action)} → ${model.id}: fields=[${Object.keys(request)\n\t\t\t\t.sort()\n\t\t\t\t.join(',')}] bytes=${body.length}`,\n\t\t);\n\n\t\ttry {\n\t\t\treturn await this.context.fetch(url, {\n\t\t\t\theaders: await this.getHeaders(action, apiKey, model, url, conversationId),\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: body,\n\t\t\t\tsignal: signal,\n\t\t\t});\n\t\t} catch (ex) {\n\t\t\tif (ex.name === 'AbortError') throw new CancellationError(ex);\n\t\t\tthrow ex;\n\t\t}\n\t}\n}\n\nexport interface ChatCompletionRequest {\n\tmodel: string;\n\t/** Provider-shaped messages from {@link OpenAICompatibleProviderBase.serializeMessages} */\n\tmessages: unknown[];\n\n\t/** Anthropic carries the initial system prompt here instead of as a `system`-role message */\n\tsystem?: string;\n\n\t/** Tool definitions, shaped by {@link OpenAICompatibleProviderBase.serializeTools} */\n\ttools?: unknown[];\n\n\t/** @deprecated but used by Anthropic & Gemini */\n\tmax_tokens?: number;\n\t/** Currently can't be used for Anthropic & Gemini */\n\tmax_completion_tokens?: number;\n\tmetadata?: Record<string, string>;\n\tstream?: boolean;\n\ttemperature?: number;\n\ttop_p?: number;\n\n\t/** Not supported by many models/providers */\n\treasoning_effort?: 'low' | 'medium' | 'high';\n\n\t/** OpenAI-compatible structured output (set via {@link OpenAICompatibleProviderBase.applyResponseFormat}) */\n\tresponse_format?: { type: 'json_schema'; json_schema: { name: string; strict: boolean; schema: JSONSchema } };\n\t/** Anthropic-native structured output (set only by the Anthropic override) */\n\toutput_config?: { format: { type: 'json_schema'; schema: JSONSchema } };\n\t/** OpenRouter-only routing constraint (set only by the OpenRouter override) */\n\tprovider?: { require_parameters: boolean };\n}\n\nexport interface ChatCompletionResponse {\n\tid: string;\n\tmodel: string;\n\t/** OpenAI compatible output */\n\tchoices?: {\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: string;\n\t\t\tcontent: string | null;\n\t\t\t/** OpenAI-only; most OpenAI-compatible servers omit it entirely */\n\t\t\trefusal?: string | null;\n\t\t\ttool_calls?: {\n\t\t\t\tid: string;\n\t\t\t\ttype: string;\n\t\t\t\tfunction: { name: string; arguments: string };\n\t\t\t\t/** GitKraken proxy only — an opaque provider reasoning token to round-trip */\n\t\t\t\tthought_signature?: string;\n\t\t\t}[];\n\t\t};\n\t\tfinish_reason: string;\n\t}[];\n\t/** Anthropic output (non-text blocks, e.g. thinking, carry no text). `tool_use` blocks add the\n\t * call fields, which is why `id`/`name` are optional here — {@link parseToolCalls} narrows on\n\t * `type` and then checks them. */\n\tcontent?: { type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }[];\n\t/** Error envelope some providers/proxies return with an OK status (e.g. OpenRouter upstream failures) */\n\terror?: { message?: string; code?: number | string } | null;\n\t/** Anthropic stop reason */\n\tstop_reason?: string;\n\tusage: {\n\t\t/** OpenAI compatible */\n\t\tprompt_tokens?: number;\n\t\tcompletion_tokens?: number;\n\t\ttotal_tokens?: number;\n\n\t\t/** Anthropic */\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\n\t\t/** GitKraken */\n\t\tgk: {\n\t\t\tused: number;\n\t\t\tlimit: number;\n\t\t\tresets_on: string;\n\t\t};\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1869,6 +1869,15 @@ var GitHubApi = class {
|
|
|
1869
1869
|
} : data;
|
|
1870
1870
|
} catch (ex) {
|
|
1871
1871
|
if (ex instanceof GraphqlResponseError) {
|
|
1872
|
+
if (ex.errors?.some((e) => e.type === "RATE_LIMIT" || e.type === "RATE_LIMITED")) {
|
|
1873
|
+
let resetAt;
|
|
1874
|
+
const reset = ex.headers?.["x-ratelimit-reset"];
|
|
1875
|
+
if (reset != null) {
|
|
1876
|
+
resetAt = parseInt(reset, 10);
|
|
1877
|
+
if (Number.isNaN(resetAt)) resetAt = void 0;
|
|
1878
|
+
}
|
|
1879
|
+
throw new RequestRateLimitError(ex, accessToken, resetAt);
|
|
1880
|
+
}
|
|
1872
1881
|
if (allowPartialNotFound === "aliased" && ex.data != null && ex.errors != null && ex.errors.length > 0) {
|
|
1873
1882
|
if (ex.errors.every((e) => e.path != null && e.path.length > 0 && e.path[0] in ex.data)) return {
|
|
1874
1883
|
data: ex.data,
|
|
@@ -1878,16 +1887,6 @@ var GitHubApi = class {
|
|
|
1878
1887
|
switch (ex.errors?.[0]?.type) {
|
|
1879
1888
|
case "NOT_FOUND": throw new RequestNotFoundError(ex);
|
|
1880
1889
|
case "FORBIDDEN": throw new AuthenticationError(tokenInfo, 3, ex);
|
|
1881
|
-
case "RATE_LIMIT":
|
|
1882
|
-
case "RATE_LIMITED": {
|
|
1883
|
-
let resetAt;
|
|
1884
|
-
const reset = ex.headers?.["x-ratelimit-reset"];
|
|
1885
|
-
if (reset != null) {
|
|
1886
|
-
resetAt = parseInt(reset, 10);
|
|
1887
|
-
if (Number.isNaN(resetAt)) resetAt = void 0;
|
|
1888
|
-
}
|
|
1889
|
-
throw new RequestRateLimitError(ex, accessToken, resetAt);
|
|
1890
|
-
}
|
|
1891
1890
|
}
|
|
1892
1891
|
if (_logger.isDebugging) this.config.onDebugError?.(`GitHub request failed: ${ex.errors?.[0]?.message ?? ex.message}`);
|
|
1893
1892
|
} else if (ex instanceof RequestError || ex.name === "AbortError") this.handleRequestError(provider, token, ex, scope, allowPartialNotFound === "aliased");
|
|
@@ -1992,18 +1991,18 @@ var GitHubApi = class {
|
|
|
1992
1991
|
throw new AuthenticationError(tokenInfo, 3, ex);
|
|
1993
1992
|
case 500:
|
|
1994
1993
|
scope?.error(ex);
|
|
1995
|
-
if (ex.response != null) reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, provider == null || provider.id === "github" ? l10n.t("{0} failed to respond and might be experiencing issues. Please visit the [GitHub status page](https://githubstatus.com) for more information.", provider?.name ?? "GitHub") : l10n.t("{0} failed to respond and might be experiencing issues.", provider.name)), deferFailure);
|
|
1994
|
+
if (ex.response != null) reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, provider == null || provider.id === "github" ? l10n.t("{0} failed to respond and might be experiencing issues. Please visit the [GitHub status page](https://githubstatus.com) for more information.", provider?.name ?? "GitHub") : l10n.t("{0} failed to respond and might be experiencing issues.", provider.name)), deferFailure, token.connectionId);
|
|
1996
1995
|
return;
|
|
1997
1996
|
case 502:
|
|
1998
1997
|
scope?.error(ex);
|
|
1999
1998
|
if (ex.message.includes("timeout")) {
|
|
2000
|
-
reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, `${provider?.name ?? "GitHub"} request timed out`), deferFailure);
|
|
1999
|
+
reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, `${provider?.name ?? "GitHub"} request timed out`), deferFailure, token.connectionId);
|
|
2001
2000
|
return;
|
|
2002
2001
|
}
|
|
2003
2002
|
break;
|
|
2004
2003
|
case 503:
|
|
2005
2004
|
scope?.error(ex);
|
|
2006
|
-
reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, provider == null || provider.id === "github" ? l10n.t("{0} failed to respond and might be experiencing issues. Please visit the [GitHub status page](https://githubstatus.com) for more information.", provider?.name ?? "GitHub") : l10n.t("{0} failed to respond and might be experiencing issues.", provider.name)), deferFailure);
|
|
2005
|
+
reportRequestFailure(provider, ex, () => this.config.onRequestError?.(provider, provider == null || provider.id === "github" ? l10n.t("{0} failed to respond and might be experiencing issues. Please visit the [GitHub status page](https://githubstatus.com) for more information.", provider?.name ?? "GitHub") : l10n.t("{0} failed to respond and might be experiencing issues.", provider.name)), deferFailure, token.connectionId);
|
|
2007
2006
|
return;
|
|
2008
2007
|
default: if (ex.status >= 400 && ex.status < 500) throw new RequestClientError(ex);
|
|
2009
2008
|
}
|