@gajae-code/ai 0.18.11 → 0.18.12
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 +68 -0
- package/README.md +1 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +30 -0
- package/dist/types/auth-storage.d.ts +80 -8
- package/dist/types/bedrock-claude-cache-policy.d.ts +1 -1
- package/dist/types/model-cache.d.ts +6 -4
- package/dist/types/model-manager.d.ts +2 -0
- package/dist/types/openai-completions-compat.d.ts +2 -1
- package/dist/types/providers/anthropic.d.ts +10 -0
- package/dist/types/providers/aws-credential-config.d.ts +5 -0
- package/dist/types/providers/google-auth.d.ts +7 -0
- package/dist/types/providers/google-gemini-headers.d.ts +10 -5
- package/dist/types/providers/kiro-api-key.d.ts +5 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +2 -1
- package/dist/types/providers/openai-completions.d.ts +1 -1
- package/dist/types/providers/openai-responses-shared.d.ts +2 -1
- package/dist/types/stream.d.ts +33 -1
- package/dist/types/types.d.ts +26 -3
- package/dist/types/usage.d.ts +3 -0
- package/dist/types/utils/discovery/antigravity.d.ts +11 -0
- package/dist/types/utils/idle-iterator.d.ts +3 -2
- package/dist/types/utils/oauth/types.d.ts +9 -0
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +62 -11
- package/src/auth-broker/wire-schemas.ts +2 -0
- package/src/auth-gateway/server.ts +3 -2
- package/src/auth-storage.ts +647 -194
- package/src/bedrock-claude-cache-policy.d.ts +1 -1
- package/src/bedrock-claude-cache-policy.ts +2 -2
- package/src/model-cache.ts +45 -10
- package/src/model-manager.ts +18 -6
- package/src/model-thinking.ts +37 -7
- package/src/models.json +2931 -1670
- package/src/openai-completions-compat.d.ts +2 -1
- package/src/openai-completions-compat.ts +5 -0
- package/src/provider-models/google.ts +7 -1
- package/src/provider-models/openai-compat.ts +26 -0
- package/src/providers/amazon-bedrock.ts +20 -11
- package/src/providers/anthropic.d.ts +10 -0
- package/src/providers/anthropic.ts +139 -4
- package/src/providers/aws-credential-config.d.ts +5 -0
- package/src/providers/aws-credential-config.ts +29 -1
- package/src/providers/google-auth.d.ts +7 -0
- package/src/providers/google-auth.ts +165 -4
- package/src/providers/google-gemini-headers.d.ts +10 -5
- package/src/providers/google-gemini-headers.ts +11 -6
- package/src/providers/google-vertex.ts +2 -4
- package/src/providers/kiro-api-key.d.ts +5 -0
- package/src/providers/kiro-api-key.ts +379 -68
- package/src/providers/ollama.ts +19 -9
- package/src/providers/openai-anthropic-shim.ts +1 -0
- package/src/providers/openai-chat-server-schema.ts +1 -1
- package/src/providers/openai-chat-server.ts +14 -2
- package/src/providers/openai-codex-responses.ts +21 -14
- package/src/providers/openai-completions.d.ts +1 -1
- package/src/providers/openai-completions.ts +65 -4
- package/src/providers/openai-responses-server.ts +10 -1
- package/src/providers/openai-responses-shared.d.ts +2 -1
- package/src/providers/openai-responses-shared.ts +42 -1
- package/src/providers/openai-responses.ts +51 -10
- package/src/providers/pi-native-server.ts +1 -1
- package/src/stream.d.ts +33 -1
- package/src/stream.ts +107 -13
- package/src/types.d.ts +26 -3
- package/src/types.ts +55 -2
- package/src/usage/claude.ts +4 -2
- package/src/usage.ts +3 -0
- package/src/utils/discovery/antigravity.ts +144 -2
- package/src/utils/idle-iterator.d.ts +3 -2
- package/src/utils/idle-iterator.ts +30 -10
- package/src/utils/oauth/anthropic.ts +23 -22
- package/src/utils/oauth/api-key-validation.ts +34 -11
- package/src/utils/oauth/types.d.ts +9 -0
- package/src/utils/oauth/types.ts +9 -0
- package/src/utils/parse-bind.d.ts +35 -0
|
@@ -27,13 +27,14 @@ export declare function isDirectXaiReasoningEffortModel(model: {
|
|
|
27
27
|
api?: string;
|
|
28
28
|
baseUrl?: string;
|
|
29
29
|
}, resolvedBaseUrl?: string): boolean;
|
|
30
|
-
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolChoiceSupport" | "supportsResponsesSessionAffinity" | "supportsServiceTier" | "reservedToolNames">> & {
|
|
30
|
+
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolChoiceSupport" | "supportsResponsesSessionAffinity" | "supportsServiceTier" | "supportsUltrafastTier" | "reservedToolNames">> & {
|
|
31
31
|
openRouterRouting?: OpenAICompat["openRouterRouting"];
|
|
32
32
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
33
33
|
extraBody?: OpenAICompat["extraBody"];
|
|
34
34
|
toolStrictMode: ResolvedToolStrictMode;
|
|
35
35
|
supportsResponsesSessionAffinity?: OpenAICompat["supportsResponsesSessionAffinity"];
|
|
36
36
|
supportsServiceTier?: OpenAICompat["supportsServiceTier"];
|
|
37
|
+
supportsUltrafastTier?: OpenAICompat["supportsUltrafastTier"];
|
|
37
38
|
/** Optional explicit capability override; resolved via deriveToolChoiceSupport. */
|
|
38
39
|
toolChoiceSupport?: OpenAICompat["toolChoiceSupport"];
|
|
39
40
|
};
|
|
@@ -146,6 +146,7 @@ export type ResolvedOpenAICompat = Required<
|
|
|
146
146
|
| "toolChoiceSupport"
|
|
147
147
|
| "supportsResponsesSessionAffinity"
|
|
148
148
|
| "supportsServiceTier"
|
|
149
|
+
| "supportsUltrafastTier"
|
|
149
150
|
| "reservedToolNames"
|
|
150
151
|
>
|
|
151
152
|
> & {
|
|
@@ -155,6 +156,7 @@ export type ResolvedOpenAICompat = Required<
|
|
|
155
156
|
toolStrictMode: ResolvedToolStrictMode;
|
|
156
157
|
supportsResponsesSessionAffinity?: OpenAICompat["supportsResponsesSessionAffinity"];
|
|
157
158
|
supportsServiceTier?: OpenAICompat["supportsServiceTier"];
|
|
159
|
+
supportsUltrafastTier?: OpenAICompat["supportsUltrafastTier"];
|
|
158
160
|
/** Optional explicit capability override; resolved via deriveToolChoiceSupport. */
|
|
159
161
|
toolChoiceSupport?: OpenAICompat["toolChoiceSupport"];
|
|
160
162
|
};
|
|
@@ -349,6 +351,8 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
349
351
|
supportsDeveloperRole: !isNonStandard,
|
|
350
352
|
sendSessionHeaders: false,
|
|
351
353
|
supportsResponsesSessionAffinity: false,
|
|
354
|
+
supportsServiceTier: false,
|
|
355
|
+
supportsUltrafastTier: false,
|
|
352
356
|
supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
|
|
353
357
|
supportsReasoningEffort:
|
|
354
358
|
hasAuditedReasoningEffortTransport &&
|
|
@@ -422,6 +426,7 @@ export function resolveOpenAICompat(
|
|
|
422
426
|
? model.compat.supportsResponsesSessionAffinity
|
|
423
427
|
: undefined) ?? detected.supportsResponsesSessionAffinity,
|
|
424
428
|
supportsServiceTier: model.compat.supportsServiceTier ?? detected.supportsServiceTier,
|
|
429
|
+
supportsUltrafastTier: model.compat.supportsUltrafastTier ?? detected.supportsUltrafastTier,
|
|
425
430
|
supportsMultipleSystemMessages:
|
|
426
431
|
model.compat.supportsMultipleSystemMessages ?? detected.supportsMultipleSystemMessages,
|
|
427
432
|
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelManagerOptions } from "../model-manager";
|
|
2
|
-
import { fetchAntigravityDiscoveryModels } from "../utils/discovery/antigravity";
|
|
2
|
+
import { fetchAntigravityDiscoveryModels, getDefaultAntigravityUserAgent } from "../utils/discovery/antigravity";
|
|
3
3
|
import { fetchGeminiModels } from "../utils/discovery/gemini";
|
|
4
4
|
|
|
5
5
|
export interface GoogleModelManagerConfig {
|
|
@@ -48,14 +48,17 @@ export function googleAntigravityModelManagerOptions(
|
|
|
48
48
|
config?: GoogleAntigravityModelManagerConfig,
|
|
49
49
|
): ModelManagerOptions<"google-gemini-cli"> {
|
|
50
50
|
const token = config?.oauthToken;
|
|
51
|
+
const ua = getDefaultAntigravityUserAgent();
|
|
51
52
|
return {
|
|
52
53
|
providerId: "google-antigravity",
|
|
54
|
+
discoveryUserAgent: ua,
|
|
53
55
|
...(token
|
|
54
56
|
? {
|
|
55
57
|
fetchDynamicModels: () =>
|
|
56
58
|
fetchAntigravityDiscoveryModels({
|
|
57
59
|
token,
|
|
58
60
|
endpoint: config?.endpoint,
|
|
61
|
+
userAgent: ua,
|
|
59
62
|
}),
|
|
60
63
|
}
|
|
61
64
|
: undefined),
|
|
@@ -67,8 +70,10 @@ export function googleGeminiCliModelManagerOptions(
|
|
|
67
70
|
): ModelManagerOptions<"google-gemini-cli"> {
|
|
68
71
|
const token = config?.oauthToken;
|
|
69
72
|
const endpoint = config?.endpoint ?? CLOUD_CODE_ASSIST_ENDPOINT;
|
|
73
|
+
const ua = getDefaultAntigravityUserAgent();
|
|
70
74
|
return {
|
|
71
75
|
providerId: "google-gemini-cli",
|
|
76
|
+
discoveryUserAgent: ua,
|
|
72
77
|
...(token
|
|
73
78
|
? {
|
|
74
79
|
fetchDynamicModels: async () => {
|
|
@@ -76,6 +81,7 @@ export function googleGeminiCliModelManagerOptions(
|
|
|
76
81
|
token,
|
|
77
82
|
endpoint,
|
|
78
83
|
targetProvider: "google-gemini-cli",
|
|
84
|
+
userAgent: ua,
|
|
79
85
|
});
|
|
80
86
|
if (models === null) {
|
|
81
87
|
return null;
|
|
@@ -1316,6 +1316,31 @@ export function kiloModelManagerOptions(config?: KiloModelManagerConfig): ModelM
|
|
|
1316
1316
|
provider: "kilo",
|
|
1317
1317
|
baseUrl,
|
|
1318
1318
|
apiKey,
|
|
1319
|
+
// Kilo serves the OpenRouter catalog shape: nested limits/modalities and
|
|
1320
|
+
// per-token USD string prices (GJC costs are per million tokens).
|
|
1321
|
+
mapModel: (entry, defaults) => {
|
|
1322
|
+
const topProvider = isRecord(entry.top_provider) ? entry.top_provider : undefined;
|
|
1323
|
+
const architecture = isRecord(entry.architecture) ? entry.architecture : undefined;
|
|
1324
|
+
const pricing = isRecord(entry.pricing) ? entry.pricing : undefined;
|
|
1325
|
+
const params = Array.isArray(entry.supported_parameters) ? entry.supported_parameters : [];
|
|
1326
|
+
const perMillion = (value: unknown): number => {
|
|
1327
|
+
const perToken = toNumber(value);
|
|
1328
|
+
return perToken !== undefined && perToken >= 0 ? perToken * 1_000_000 : 0;
|
|
1329
|
+
};
|
|
1330
|
+
return {
|
|
1331
|
+
...defaults,
|
|
1332
|
+
reasoning: params.includes("reasoning"),
|
|
1333
|
+
input: toInputCapabilities(architecture?.input_modalities),
|
|
1334
|
+
cost: {
|
|
1335
|
+
input: perMillion(pricing?.prompt),
|
|
1336
|
+
output: perMillion(pricing?.completion),
|
|
1337
|
+
cacheRead: perMillion(pricing?.input_cache_read),
|
|
1338
|
+
cacheWrite: perMillion(pricing?.input_cache_write),
|
|
1339
|
+
},
|
|
1340
|
+
contextWindow: toPositiveNumber(entry.context_length, defaults.contextWindow),
|
|
1341
|
+
maxTokens: toPositiveNumber(topProvider?.max_completion_tokens, defaults.maxTokens),
|
|
1342
|
+
};
|
|
1343
|
+
},
|
|
1319
1344
|
}),
|
|
1320
1345
|
};
|
|
1321
1346
|
}
|
|
@@ -2125,6 +2150,7 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
2125
2150
|
...(api === "openai-completions"
|
|
2126
2151
|
? {
|
|
2127
2152
|
compat: {
|
|
2153
|
+
...(reference.compat ?? {}),
|
|
2128
2154
|
supportsStore: false,
|
|
2129
2155
|
supportsDeveloperRole: false,
|
|
2130
2156
|
supportsReasoningEffort: false,
|
|
@@ -52,6 +52,7 @@ import { decodeEventStream } from "./aws-eventstream";
|
|
|
52
52
|
import type { AwsCredentials } from "./aws-sigv4";
|
|
53
53
|
import { signRequest } from "./aws-sigv4";
|
|
54
54
|
import { transformMessages } from "./transform-messages";
|
|
55
|
+
import { NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
|
|
55
56
|
|
|
56
57
|
export type BedrockThinkingDisplay = "summarized" | "omitted";
|
|
57
58
|
|
|
@@ -673,12 +674,24 @@ function buildSystemPrompt(
|
|
|
673
674
|
return blocks;
|
|
674
675
|
}
|
|
675
676
|
|
|
677
|
+
function convertToolResultContent(
|
|
678
|
+
content: ToolResultMessage["content"],
|
|
679
|
+
supportsImages: boolean,
|
|
680
|
+
): Array<TextBlockWire | ImageBlockWire> {
|
|
681
|
+
return content.map(c => {
|
|
682
|
+
if (c.type !== "image") return { text: c.text.toWellFormed() };
|
|
683
|
+
if (!supportsImages) return { text: NON_VISION_IMAGE_PLACEHOLDER };
|
|
684
|
+
return { image: createImageBlock(c.mimeType, c.data) };
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
676
688
|
function convertMessages(
|
|
677
689
|
context: Context,
|
|
678
690
|
model: Model<"bedrock-converse-stream">,
|
|
679
691
|
cacheRetention: CacheRetention,
|
|
680
692
|
): WireMessage[] {
|
|
681
693
|
const result: WireMessage[] = [];
|
|
694
|
+
const supportsImages = model.input.includes("image");
|
|
682
695
|
const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
|
|
683
696
|
|
|
684
697
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
@@ -702,7 +715,11 @@ function convertMessages(
|
|
|
702
715
|
break;
|
|
703
716
|
}
|
|
704
717
|
case "image":
|
|
705
|
-
contentBlocks.push(
|
|
718
|
+
contentBlocks.push(
|
|
719
|
+
supportsImages
|
|
720
|
+
? { image: createImageBlock(c.mimeType, c.data) }
|
|
721
|
+
: { text: NON_VISION_IMAGE_PLACEHOLDER },
|
|
722
|
+
);
|
|
706
723
|
break;
|
|
707
724
|
default:
|
|
708
725
|
throw new Error("Unknown user content type");
|
|
@@ -772,11 +789,7 @@ function convertMessages(
|
|
|
772
789
|
toolResults.push({
|
|
773
790
|
toolResult: {
|
|
774
791
|
toolUseId: normalizeToolCallId(m.toolCallId),
|
|
775
|
-
content: m.content
|
|
776
|
-
c.type === "image"
|
|
777
|
-
? { image: createImageBlock(c.mimeType, c.data) }
|
|
778
|
-
: { text: c.text.toWellFormed() },
|
|
779
|
-
),
|
|
792
|
+
content: convertToolResultContent(m.content, supportsImages),
|
|
780
793
|
status: m.isError ? "error" : "success",
|
|
781
794
|
},
|
|
782
795
|
});
|
|
@@ -787,11 +800,7 @@ function convertMessages(
|
|
|
787
800
|
toolResults.push({
|
|
788
801
|
toolResult: {
|
|
789
802
|
toolUseId: normalizeToolCallId(nextMsg.toolCallId),
|
|
790
|
-
content: nextMsg.content
|
|
791
|
-
c.type === "image"
|
|
792
|
-
? { image: createImageBlock(c.mimeType, c.data) }
|
|
793
|
-
: { text: c.text.toWellFormed() },
|
|
794
|
-
),
|
|
803
|
+
content: convertToolResultContent(nextMsg.content, supportsImages),
|
|
795
804
|
status: nextMsg.isError ? "error" : "success",
|
|
796
805
|
},
|
|
797
806
|
});
|
|
@@ -51,6 +51,16 @@ export declare function isAnthropicThinkingBlockMutationError(error: unknown): b
|
|
|
51
51
|
* than only the latest one.
|
|
52
52
|
*/
|
|
53
53
|
export declare function isAnthropicThinkingSignatureInvalidError(error: unknown): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 400 shape where Anthropic cannot decode one of the request's images:
|
|
56
|
+
* `{"type":"invalid_request_error","message":"Could not process image"}`.
|
|
57
|
+
* Observed for a truncated JPEG (no end-of-image marker) that local resizing
|
|
58
|
+
* also failed to decode and therefore forwarded unchanged. The body names no
|
|
59
|
+
* image, and the image stays in history, so without omission every later
|
|
60
|
+
* request of the session draws the same rejection.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isAnthropicImageUnprocessableError(error: unknown): boolean;
|
|
63
|
+
export declare const ANTHROPIC_OMITTED_IMAGE_PLACEHOLDER = "[Image omitted: Anthropic could not process this image. The file may be corrupt or truncated; re-export or convert it before attaching it again.]";
|
|
54
64
|
/**
|
|
55
65
|
* CLIProxyAPI replaces Anthropic's rejection body wholesale instead of forwarding
|
|
56
66
|
* it: the client only ever sees
|
|
@@ -388,6 +388,14 @@ type AnthropicProviderSessionState = ProviderSessionState & {
|
|
|
388
388
|
thinkingReplayRepairScope: AnthropicThinkingReplayRepairScope;
|
|
389
389
|
thinkingReplayRepairAttempts: number;
|
|
390
390
|
thinkingReplayRejectedPayload?: AnthropicPayloadFingerprint;
|
|
391
|
+
/**
|
|
392
|
+
* sha256 digests of base64 image payloads Anthropic rejected with
|
|
393
|
+
* "Could not process image". The image stays in session history, so every
|
|
394
|
+
* later build must keep replacing it or each turn draws the same 400.
|
|
395
|
+
*/
|
|
396
|
+
omittedImageDigests: Set<string>;
|
|
397
|
+
/** Image omissions spent since the last completed stream; bounds escalation. */
|
|
398
|
+
imageOmissionRepairs: number;
|
|
391
399
|
/**
|
|
392
400
|
* Managed-mode escalation for the CPA alias-restore failure (issue #4338):
|
|
393
401
|
* corrective steering recorded against one exact turn, applied by the next
|
|
@@ -416,6 +424,8 @@ function createAnthropicProviderSessionState(): AnthropicProviderSessionState {
|
|
|
416
424
|
warnedUnknownLongCacheRetention: new Set(),
|
|
417
425
|
thinkingReplayRepairScope: "none",
|
|
418
426
|
thinkingReplayRepairAttempts: 0,
|
|
427
|
+
omittedImageDigests: new Set(),
|
|
428
|
+
imageOmissionRepairs: 0,
|
|
419
429
|
close: () => {
|
|
420
430
|
state.strictToolsDisabled = false;
|
|
421
431
|
state.fastModeDisabled = false;
|
|
@@ -424,6 +434,8 @@ function createAnthropicProviderSessionState(): AnthropicProviderSessionState {
|
|
|
424
434
|
state.thinkingReplayRepairScope = "none";
|
|
425
435
|
state.thinkingReplayRepairAttempts = 0;
|
|
426
436
|
state.thinkingReplayRejectedPayload = undefined;
|
|
437
|
+
state.omittedImageDigests.clear();
|
|
438
|
+
state.imageOmissionRepairs = 0;
|
|
427
439
|
state.cpaToolAliasSteering = undefined;
|
|
428
440
|
},
|
|
429
441
|
};
|
|
@@ -532,6 +544,86 @@ export function isAnthropicThinkingSignatureInvalidError(error: unknown): boolea
|
|
|
532
544
|
);
|
|
533
545
|
}
|
|
534
546
|
|
|
547
|
+
/**
|
|
548
|
+
* 400 shape where Anthropic cannot decode one of the request's images:
|
|
549
|
+
* `{"type":"invalid_request_error","message":"Could not process image"}`.
|
|
550
|
+
* Observed for a truncated JPEG (no end-of-image marker) that local resizing
|
|
551
|
+
* also failed to decode and therefore forwarded unchanged. The body names no
|
|
552
|
+
* image, and the image stays in history, so without omission every later
|
|
553
|
+
* request of the session draws the same rejection.
|
|
554
|
+
*/
|
|
555
|
+
export function isAnthropicImageUnprocessableError(error: unknown): boolean {
|
|
556
|
+
if (!isAnthropicInvalidRequestStatus(error)) return false;
|
|
557
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
558
|
+
return /invalid_request_error/i.test(message) && /could not process image/i.test(message);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Bounds image omission per stream: the first rejection omits the images of the
|
|
563
|
+
* newest message that carries any (the request before it was accepted, so the
|
|
564
|
+
* rejected image is almost always new), the second omits every remaining image.
|
|
565
|
+
*/
|
|
566
|
+
const ANTHROPIC_MAX_IMAGE_OMISSION_REPAIRS = 2;
|
|
567
|
+
|
|
568
|
+
export const ANTHROPIC_OMITTED_IMAGE_PLACEHOLDER =
|
|
569
|
+
"[Image omitted: Anthropic could not process this image. The file may be corrupt or truncated; re-export or convert it before attaching it again.]";
|
|
570
|
+
|
|
571
|
+
type AnthropicImageSite = {
|
|
572
|
+
digest: string;
|
|
573
|
+
messageIndex: number;
|
|
574
|
+
container: unknown[];
|
|
575
|
+
index: number;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
function collectAnthropicImageSites(params: MessageCreateParamsStreaming): AnthropicImageSite[] {
|
|
579
|
+
const sites: AnthropicImageSite[] = [];
|
|
580
|
+
const visit = (container: unknown[], messageIndex: number): void => {
|
|
581
|
+
for (let index = 0; index < container.length; index++) {
|
|
582
|
+
const block = container[index];
|
|
583
|
+
if (!isRecord(block)) continue;
|
|
584
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
585
|
+
visit(block.content, messageIndex);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (block.type !== "image" || !isRecord(block.source)) continue;
|
|
589
|
+
const data = block.source.data;
|
|
590
|
+
if (typeof data !== "string") continue;
|
|
591
|
+
const digest = nodeCrypto.createHash("sha256").update(data).digest("hex");
|
|
592
|
+
sites.push({ digest, messageIndex, container, index });
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
params.messages.forEach((message, messageIndex) => {
|
|
596
|
+
if (Array.isArray(message.content)) visit(message.content as unknown[], messageIndex);
|
|
597
|
+
});
|
|
598
|
+
return sites;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Replace every image whose payload digest is listed, keeping its cache breakpoint. */
|
|
602
|
+
function omitAnthropicImages(params: MessageCreateParamsStreaming, digests: ReadonlySet<string>): void {
|
|
603
|
+
for (const site of collectAnthropicImageSites(params)) {
|
|
604
|
+
if (!digests.has(site.digest)) continue;
|
|
605
|
+
const image = site.container[site.index] as Record<string, unknown>;
|
|
606
|
+
site.container[site.index] = {
|
|
607
|
+
type: "text",
|
|
608
|
+
text: ANTHROPIC_OMITTED_IMAGE_PLACEHOLDER,
|
|
609
|
+
...(image.cache_control === undefined ? {} : { cache_control: image.cache_control }),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Digests to omit after a rejection: the newest image-bearing message first, then everything. */
|
|
615
|
+
function selectAnthropicImagesToOmit(
|
|
616
|
+
params: MessageCreateParamsStreaming,
|
|
617
|
+
omitted: ReadonlySet<string>,
|
|
618
|
+
scope: "latest" | "all",
|
|
619
|
+
): string[] {
|
|
620
|
+
const remaining = collectAnthropicImageSites(params).filter(site => !omitted.has(site.digest));
|
|
621
|
+
if (remaining.length === 0) return [];
|
|
622
|
+
const newest = Math.max(...remaining.map(site => site.messageIndex));
|
|
623
|
+
const selected = scope === "latest" ? remaining.filter(site => site.messageIndex === newest) : remaining;
|
|
624
|
+
return [...new Set(selected.map(site => site.digest))];
|
|
625
|
+
}
|
|
626
|
+
|
|
535
627
|
/**
|
|
536
628
|
* CLIProxyAPI replaces Anthropic's rejection body wholesale instead of forwarding
|
|
537
629
|
* it: the client only ever sees
|
|
@@ -1640,6 +1732,7 @@ function getAnthropicCompat(
|
|
|
1640
1732
|
supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? true,
|
|
1641
1733
|
supportsToolChoice: model.compat?.supportsToolChoice ?? true,
|
|
1642
1734
|
supportsForcedToolChoice: model.compat?.supportsForcedToolChoice ?? true,
|
|
1735
|
+
supportsUltrafastTier: model.compat?.supportsUltrafastTier ?? false,
|
|
1643
1736
|
promptCacheMode: model.compat?.promptCacheMode ?? "none",
|
|
1644
1737
|
toolChoiceSupport: model.compat?.toolChoiceSupport,
|
|
1645
1738
|
};
|
|
@@ -2095,6 +2188,9 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2095
2188
|
let activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
2096
2189
|
|
|
2097
2190
|
try {
|
|
2191
|
+
const idleTimeoutMs =
|
|
2192
|
+
options?.streamIdleTimeoutMs ??
|
|
2193
|
+
getStreamIdleTimeoutMs(model.streamIdleTimeoutMs ?? getProviderStreamIdleTimeoutFallbackMs(model.provider));
|
|
2098
2194
|
let client: Anthropic;
|
|
2099
2195
|
let isOAuthToken: boolean;
|
|
2100
2196
|
|
|
@@ -2127,7 +2223,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2127
2223
|
requestMaxRetries: options?.requestMaxRetries,
|
|
2128
2224
|
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
2129
2225
|
streamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,
|
|
2130
|
-
streamIdleTimeoutMs:
|
|
2226
|
+
streamIdleTimeoutMs: idleTimeoutMs,
|
|
2131
2227
|
providerSessionId: options?.providerSessionId,
|
|
2132
2228
|
});
|
|
2133
2229
|
client = created.client;
|
|
@@ -2154,6 +2250,10 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2154
2250
|
let thinkingReplayRepairPersistent = thinkingReplayRepairScope !== "none";
|
|
2155
2251
|
let generatedCacheBudget: GeneratedCacheBudget = providerSessionState?.generatedCacheBudget ?? 2;
|
|
2156
2252
|
let warnedUnknownLongCacheRetention = false;
|
|
2253
|
+
// Shared with the session so later turns keep omitting a rejected image
|
|
2254
|
+
// that is still in history; per-stream when no session state is shared.
|
|
2255
|
+
const omittedImageDigests = providerSessionState?.omittedImageDigests ?? new Set<string>();
|
|
2256
|
+
let imageOmissionRepairs = providerSessionState?.imageOmissionRepairs ?? 0;
|
|
2157
2257
|
const prepareParams = async (): Promise<MessageCreateParamsStreaming> => {
|
|
2158
2258
|
// Degradation state is cumulative: every fallback rebuild must merge all
|
|
2159
2259
|
// repairs activated so far. Rebuilding from only the immediate call lets
|
|
@@ -2196,6 +2296,9 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2196
2296
|
if (dropFastMode) {
|
|
2197
2297
|
dropAnthropicFastMode(nextParams);
|
|
2198
2298
|
}
|
|
2299
|
+
if (omittedImageDigests.size > 0) {
|
|
2300
|
+
omitAnthropicImages(nextParams, omittedImageDigests);
|
|
2301
|
+
}
|
|
2199
2302
|
const replacementPayload = await options?.onPayload?.(
|
|
2200
2303
|
nextParams,
|
|
2201
2304
|
model,
|
|
@@ -2315,9 +2418,6 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2315
2418
|
firstTokenTime = undefined;
|
|
2316
2419
|
truncatedToolCalls.clear();
|
|
2317
2420
|
};
|
|
2318
|
-
const idleTimeoutMs =
|
|
2319
|
-
options?.streamIdleTimeoutMs ??
|
|
2320
|
-
getStreamIdleTimeoutMs(getProviderStreamIdleTimeoutFallbackMs(model.provider));
|
|
2321
2421
|
const firstEventFallbackMs = getProviderFirstEventTimeoutFallbackMs(model.provider);
|
|
2322
2422
|
const firstEventTimeoutMs =
|
|
2323
2423
|
options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs, firstEventFallbackMs);
|
|
@@ -2786,6 +2886,11 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2786
2886
|
providerSessionState.thinkingReplayRepairScope = "none";
|
|
2787
2887
|
providerSessionState.thinkingReplayRepairAttempts = 0;
|
|
2788
2888
|
}
|
|
2889
|
+
// Omitted digests stay recorded (the images are still in history); only
|
|
2890
|
+
// the escalation budget re-arms for a different image rejected later.
|
|
2891
|
+
if (providerSessionState && imageOmissionRepairs > 0) {
|
|
2892
|
+
providerSessionState.imageOmissionRepairs = 0;
|
|
2893
|
+
}
|
|
2789
2894
|
// Release a recorded CPA steering once any stream completes: a
|
|
2790
2895
|
// successful stream consumed it, and a failed one ends the turn (a
|
|
2791
2896
|
// later turn's different user prompt would expire it anyway).
|
|
@@ -2889,6 +2994,36 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2889
2994
|
resetOutputForRetry();
|
|
2890
2995
|
continue;
|
|
2891
2996
|
}
|
|
2997
|
+
// An image Anthropic cannot decode fails the whole request and stays in
|
|
2998
|
+
// history, so the session never recovers on its own. Record the omission
|
|
2999
|
+
// before deciding who retries: unmanaged streams resend here, managed
|
|
3000
|
+
// attempts leave the retry to the fallback controller, whose next attempt
|
|
3001
|
+
// rebuilds from the shared session state without the rejected image.
|
|
3002
|
+
if (
|
|
3003
|
+
firstTokenTime === undefined &&
|
|
3004
|
+
imageOmissionRepairs < ANTHROPIC_MAX_IMAGE_OMISSION_REPAIRS &&
|
|
3005
|
+
isAnthropicImageUnprocessableError(streamFailure)
|
|
3006
|
+
) {
|
|
3007
|
+
const scope = imageOmissionRepairs === 0 ? "latest" : "all";
|
|
3008
|
+
const digests = selectAnthropicImagesToOmit(params, omittedImageDigests, scope);
|
|
3009
|
+
if (digests.length > 0) {
|
|
3010
|
+
for (const digest of digests) omittedImageDigests.add(digest);
|
|
3011
|
+
imageOmissionRepairs++;
|
|
3012
|
+
if (providerSessionState) providerSessionState.imageOmissionRepairs = imageOmissionRepairs;
|
|
3013
|
+
logger.warn("anthropic: provider could not process an image; omitting it from the request", {
|
|
3014
|
+
model: model.id,
|
|
3015
|
+
scope,
|
|
3016
|
+
omittedImages: digests.length,
|
|
3017
|
+
managed: options?.fallbackManaged === true,
|
|
3018
|
+
});
|
|
3019
|
+
if (!options?.fallbackManaged && !options?.disableProviderRetries) {
|
|
3020
|
+
params = await prepareParams();
|
|
3021
|
+
providerUploadCount++;
|
|
3022
|
+
resetOutputForRetry();
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
2892
3027
|
const thinkingSignatureInvalid = isAnthropicThinkingSignatureInvalidError(streamFailure);
|
|
2893
3028
|
const thinkingBlocksImmutable = isAnthropicThinkingBlockMutationError(streamFailure);
|
|
2894
3029
|
const maskedProxyRejection = isAnthropicMaskedProxyRejection(streamFailure);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FetchImpl } from "../types";
|
|
1
2
|
import type { AwsCredentials } from "./aws-sigv4";
|
|
2
3
|
export type AwsIniFile = Record<string, Record<string, string>>;
|
|
3
4
|
export interface AwsCredentialSourceOptions {
|
|
@@ -16,4 +17,8 @@ export declare function classifyAwsProfileCapability(profile: string, credential
|
|
|
16
17
|
export declare function hasResolvableAwsProfileSource(options?: AwsCredentialSourceOptions & {
|
|
17
18
|
onScan?: () => void;
|
|
18
19
|
}, now?: number): boolean;
|
|
20
|
+
/** Probe only the IMDSv2 token and role endpoints; never request role credentials for status. */
|
|
21
|
+
export declare function hasAwsInstanceProfileCredentialSource(options?: {
|
|
22
|
+
fetch?: FetchImpl;
|
|
23
|
+
}): Promise<boolean>;
|
|
19
24
|
export declare function isValidBedrockBearerToken(token: string | undefined): token is string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { $credentialEnv, getTrustedHomeDir } from "@gajae-code/utils";
|
|
3
|
+
import { $credentialEnv, $env, getTrustedHomeDir } from "@gajae-code/utils";
|
|
4
|
+
import type { FetchImpl } from "../types";
|
|
4
5
|
import type { AwsCredentials } from "./aws-sigv4";
|
|
5
6
|
|
|
6
7
|
export type AwsIniFile = Record<string, Record<string, string>>;
|
|
@@ -122,6 +123,33 @@ export function hasResolvableAwsProfileSource(
|
|
|
122
123
|
return value;
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
/** Probe only the IMDSv2 token and role endpoints; never request role credentials for status. */
|
|
127
|
+
export async function hasAwsInstanceProfileCredentialSource(options?: { fetch?: FetchImpl }): Promise<boolean> {
|
|
128
|
+
if ($env.AWS_EC2_METADATA_DISABLED?.toLowerCase() === "true") return false;
|
|
129
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
130
|
+
const signal = AbortSignal.timeout(1000);
|
|
131
|
+
try {
|
|
132
|
+
const tokenResponse = await fetchImpl("http://169.254.169.254/latest/api/token", {
|
|
133
|
+
method: "PUT",
|
|
134
|
+
headers: { "x-aws-ec2-metadata-token-ttl-seconds": "21600" },
|
|
135
|
+
redirect: "error",
|
|
136
|
+
signal,
|
|
137
|
+
});
|
|
138
|
+
if (!tokenResponse.ok) return false;
|
|
139
|
+
const token = (await tokenResponse.text()).trim();
|
|
140
|
+
if (!token) return false;
|
|
141
|
+
|
|
142
|
+
const rolesResponse = await fetchImpl("http://169.254.169.254/latest/meta-data/iam/security-credentials/", {
|
|
143
|
+
headers: { "x-aws-ec2-metadata-token": token },
|
|
144
|
+
redirect: "error",
|
|
145
|
+
signal,
|
|
146
|
+
});
|
|
147
|
+
return rolesResponse.ok && Boolean((await rolesResponse.text()).trim());
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
125
153
|
export function isValidBedrockBearerToken(token: string | undefined): token is string {
|
|
126
154
|
if (!token) return false;
|
|
127
155
|
return !/[\x00-\x1f\x7f]/.test(token);
|
|
@@ -14,8 +14,15 @@
|
|
|
14
14
|
* (default 60s). Concurrent callers waiting on a refresh share the same in-flight promise.
|
|
15
15
|
*/
|
|
16
16
|
import type { FetchImpl } from "../types";
|
|
17
|
+
export type VertexCredentialSource = "explicit-file" | "default-file" | "metadata-server";
|
|
18
|
+
/** Match the region-label constraint used before Vertex interpolates a location into the request host. */
|
|
19
|
+
export declare function isValidVertexLocation(location: string): boolean;
|
|
17
20
|
/** Test seam: the ADC credentials file path as resolved from trusted env. */
|
|
18
21
|
export declare function resolveAdcCredentialsPathForTest(): string | undefined;
|
|
22
|
+
/** Resolve the source of a currently usable ADC credential without acquiring an access token. */
|
|
23
|
+
export declare function getVertexCredentialSource(options?: {
|
|
24
|
+
fetch?: FetchImpl;
|
|
25
|
+
}): Promise<VertexCredentialSource | undefined>;
|
|
19
26
|
/**
|
|
20
27
|
* Returns a Bearer access token suitable for the `Authorization` header on Vertex AI calls.
|
|
21
28
|
* The token is cached in module scope and refreshed `GOOGLE_VERTEX_REFRESH_SKEW_MS` ms before it expires.
|