@gajae-code/ai 0.16.1 → 0.16.4
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 +25 -0
- package/dist/types/auth-gateway/server.d.ts +4 -2
- package/dist/types/bedrock-claude-cache-policy.d.ts +21 -0
- package/dist/types/openai-completions-compat.d.ts +22 -0
- package/dist/types/providers/amazon-bedrock.d.ts +17 -2
- package/dist/types/providers/anthropic.d.ts +1 -0
- package/dist/types/providers/openai-completions-compat.d.ts +1 -1
- package/dist/types/providers/opencode-go-session.d.ts +5 -0
- package/dist/types/types.d.ts +7 -0
- package/dist/types/utils/codex-entitlement.d.ts +22 -0
- package/package.json +3 -3
- package/src/auth-gateway/server.ts +5 -0
- package/src/auth-storage.ts +31 -8
- package/src/bedrock-claude-cache-policy.d.ts +21 -0
- package/src/bedrock-claude-cache-policy.ts +68 -0
- package/src/model-pricing.ts +11 -0
- package/src/model-thinking.ts +31 -7
- package/src/models.json +37 -0
- package/src/openai-completions-compat.d.ts +22 -0
- package/src/openai-completions-compat.ts +77 -3
- package/src/providers/amazon-bedrock.d.ts +17 -2
- package/src/providers/amazon-bedrock.ts +9 -9
- package/src/providers/anthropic.d.ts +1 -0
- package/src/providers/anthropic.ts +38 -21
- package/src/providers/cursor.ts +12 -6
- package/src/providers/openai-codex/response-handler.ts +1 -1
- package/src/providers/openai-codex-responses.ts +14 -5
- package/src/providers/openai-completions-compat.d.ts +1 -1
- package/src/providers/openai-completions-compat.ts +8 -1
- package/src/providers/openai-completions.ts +14 -1
- package/src/providers/openai-responses.ts +11 -1
- package/src/providers/opencode-go-session.d.ts +5 -0
- package/src/providers/opencode-go-session.ts +57 -0
- package/src/stream.ts +1 -0
- package/src/types.d.ts +7 -0
- package/src/types.ts +7 -0
- package/src/utils/codex-entitlement.d.ts +22 -0
- package/src/utils/codex-entitlement.ts +57 -0
- package/src/utils/discovery/antigravity.ts +2 -1
- package/src/utils/discovery/gemini.ts +2 -1
|
@@ -48,10 +48,57 @@ function parseHostname(baseUrl: string): string | undefined {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function isProductionXaiApiUrl(baseUrl: string): boolean {
|
|
52
|
+
if (
|
|
53
|
+
/[\u0000-\u0020\u007f]/u.test(baseUrl) ||
|
|
54
|
+
baseUrl.includes("?") ||
|
|
55
|
+
baseUrl.includes("#") ||
|
|
56
|
+
baseUrl.includes("\\")
|
|
57
|
+
) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const authorityTail = baseUrl.slice("https://".length);
|
|
61
|
+
const authorityEnd = authorityTail.search(/[/?#]/u);
|
|
62
|
+
const authority = authorityTail.slice(0, authorityEnd === -1 ? undefined : authorityEnd);
|
|
63
|
+
if (!/^api\.x\.ai(?::443)?$/iu.test(authority)) return false;
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(baseUrl);
|
|
66
|
+
return (
|
|
67
|
+
url.protocol === "https:" &&
|
|
68
|
+
url.hostname.toLowerCase() === "api.x.ai" &&
|
|
69
|
+
url.port === "" &&
|
|
70
|
+
url.username === "" &&
|
|
71
|
+
url.password === "" &&
|
|
72
|
+
url.search === "" &&
|
|
73
|
+
url.hash === ""
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
51
80
|
function hostnameMatches(hostname: string | undefined, suffix: string): boolean {
|
|
52
81
|
return hostname !== undefined && (hostname === suffix || hostname.endsWith(`.${suffix}`));
|
|
53
82
|
}
|
|
54
83
|
|
|
84
|
+
export type GrokGeneration = {
|
|
85
|
+
major: number;
|
|
86
|
+
minor: number;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function parseGrokGeneration(modelId: string): GrokGeneration | undefined {
|
|
90
|
+
const canonicalId = modelId.startsWith("x-ai/") ? modelId.slice("x-ai/".length) : modelId;
|
|
91
|
+
const match =
|
|
92
|
+
/^grok-([1-9]\d?)(?:\.(0|[1-9]\d?))?(?:-(latest|preview|\d{8}|reasoning|\d{4}-reasoning|beta-latest-reasoning|multi-agent-beta-latest))?$/.exec(
|
|
93
|
+
canonicalId,
|
|
94
|
+
);
|
|
95
|
+
if (!match) return undefined;
|
|
96
|
+
return {
|
|
97
|
+
major: Number(match[1]),
|
|
98
|
+
minor: match[2] === undefined ? 0 : Number(match[2]),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
55
102
|
/** Returns whether the request endpoint is an audited reasoning-control transport. */
|
|
56
103
|
export function isAuditedOpenAIReasoningTransport(
|
|
57
104
|
model: { provider: string; baseUrl?: string },
|
|
@@ -63,6 +110,32 @@ export function isAuditedOpenAIReasoningTransport(
|
|
|
63
110
|
return AUDITED_REASONING_EFFORT_HOST_SUFFIXES.some(suffix => hostnameMatches(hostname, suffix));
|
|
64
111
|
}
|
|
65
112
|
|
|
113
|
+
/**
|
|
114
|
+
* xAI's first-party API accepts `reasoning_effort` on Grok 4.5 and later.
|
|
115
|
+
* Provider labels are user-configurable, so both the provider and the official
|
|
116
|
+
* API origin must match. Unknown variants fail closed instead of inheriting a
|
|
117
|
+
* capability from a loose model-id prefix.
|
|
118
|
+
*/
|
|
119
|
+
export function parseDirectXaiReasoningEffortGeneration(
|
|
120
|
+
model: { provider: string; id: string; api?: string; baseUrl?: string },
|
|
121
|
+
resolvedBaseUrl?: string,
|
|
122
|
+
): GrokGeneration | undefined {
|
|
123
|
+
if (model.provider !== "xai") return undefined;
|
|
124
|
+
if (model.api !== "openai-completions") return undefined;
|
|
125
|
+
if (!isProductionXaiApiUrl(resolvedBaseUrl ?? model.baseUrl ?? "")) return undefined;
|
|
126
|
+
if (model.id.includes("/")) return undefined;
|
|
127
|
+
const generation = parseGrokGeneration(model.id);
|
|
128
|
+
if (!generation) return undefined;
|
|
129
|
+
return generation.major > 4 || (generation.major === 4 && generation.minor >= 5) ? generation : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isDirectXaiReasoningEffortModel(
|
|
133
|
+
model: { provider: string; id: string; api?: string; baseUrl?: string },
|
|
134
|
+
resolvedBaseUrl?: string,
|
|
135
|
+
): boolean {
|
|
136
|
+
return parseDirectXaiReasoningEffortGeneration(model, resolvedBaseUrl) !== undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
66
139
|
export type ResolvedOpenAICompat = Required<
|
|
67
140
|
Omit<
|
|
68
141
|
OpenAICompat,
|
|
@@ -184,8 +257,8 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
184
257
|
baseUrl.includes("chutes.ai") ||
|
|
185
258
|
baseUrl.includes("fireworks.ai") ||
|
|
186
259
|
isDirectDeepseekApi;
|
|
187
|
-
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
|
188
|
-
|
|
260
|
+
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai") || parseGrokGeneration(model.id) !== undefined;
|
|
261
|
+
|
|
189
262
|
const isMistral = provider === "mistral" || baseUrl.includes("mistral.ai");
|
|
190
263
|
|
|
191
264
|
// Hosts whose chat-completions endpoints are known to accept multiple
|
|
@@ -278,7 +351,8 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
278
351
|
supportsResponsesSessionAffinity: false,
|
|
279
352
|
supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
|
|
280
353
|
supportsReasoningEffort:
|
|
281
|
-
hasAuditedReasoningEffortTransport &&
|
|
354
|
+
hasAuditedReasoningEffortTransport &&
|
|
355
|
+
((!isGrok && !isZai) || isOpenRouter || isDirectXaiReasoningEffortModel(model, resolvedBaseUrl)),
|
|
282
356
|
reasoningEffortMap,
|
|
283
357
|
supportsUsageInStreaming: !isCerebras,
|
|
284
358
|
disableReasoningOnForcedToolChoice: isKimiModel || isAnthropicModel || isOpenCodeGoReasoning,
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* No `@aws-sdk/*`, no `@smithy/*`, no `proxy-agent`. Proxies are honored via
|
|
7
7
|
* Bun's native `HTTPS_PROXY` support.
|
|
8
8
|
*/
|
|
9
|
+
import { parseBedrockClaudeGeneration } from "../bedrock-claude-cache-policy";
|
|
9
10
|
import type { Effort } from "../model-thinking";
|
|
10
|
-
import type { StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
|
|
11
|
+
import type { Model, StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
|
|
11
12
|
export type BedrockThinkingDisplay = "summarized" | "omitted";
|
|
12
13
|
export interface BedrockOptions extends StreamOptions {
|
|
13
14
|
region?: string;
|
|
@@ -51,10 +52,24 @@ interface WireToolConfig {
|
|
|
51
52
|
toolChoice?: WireToolChoice;
|
|
52
53
|
}
|
|
53
54
|
export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
|
|
55
|
+
/**
|
|
56
|
+
* Check if the model supports prompt caching.
|
|
57
|
+
* Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
|
|
58
|
+
* generation:
|
|
59
|
+
* https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
60
|
+
*
|
|
61
|
+
* For base models and system-defined inference profiles the model ID / ARN
|
|
62
|
+
* contains the model name, so we can decide locally.
|
|
63
|
+
*
|
|
64
|
+
* For application inference profiles (whose ARNs don't contain the model name),
|
|
65
|
+
* set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
|
|
66
|
+
* have automatic caching and don't need explicit cache points.
|
|
67
|
+
*/
|
|
68
|
+
export declare function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean;
|
|
69
|
+
export { parseBedrockClaudeGeneration };
|
|
54
70
|
export declare function stripBedrockForcedToolChoiceForRetry<T extends {
|
|
55
71
|
toolConfig?: {
|
|
56
72
|
toolChoice?: unknown;
|
|
57
73
|
};
|
|
58
74
|
}>(body: T): T;
|
|
59
75
|
export declare function convertToolConfig(tools: Tool[] | undefined, toolChoice: BedrockOptions["toolChoice"]): WireToolConfig | undefined;
|
|
60
|
-
export {};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { $credentialEnv, $env, $flag, extractHttpStatusFromError, fetchWithRetry } from "@gajae-code/utils";
|
|
11
11
|
import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
|
|
12
|
+
import { parseBedrockClaudeGeneration, supportsBedrockClaudePromptCaching } from "../bedrock-claude-cache-policy";
|
|
12
13
|
import type { Effort } from "../model-thinking";
|
|
13
14
|
import {
|
|
14
15
|
mapEffortToAnthropicAdaptiveEffort,
|
|
@@ -610,7 +611,9 @@ function handleContentBlockStop(
|
|
|
610
611
|
|
|
611
612
|
/**
|
|
612
613
|
* Check if the model supports prompt caching.
|
|
613
|
-
* Supported:
|
|
614
|
+
* Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
|
|
615
|
+
* generation:
|
|
616
|
+
* https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
614
617
|
*
|
|
615
618
|
* For base models and system-defined inference profiles the model ID / ARN
|
|
616
619
|
* contains the model name, so we can decide locally.
|
|
@@ -619,21 +622,18 @@ function handleContentBlockStop(
|
|
|
619
622
|
* set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
|
|
620
623
|
* have automatic caching and don't need explicit cache points.
|
|
621
624
|
*/
|
|
622
|
-
function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean {
|
|
625
|
+
export function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean {
|
|
626
|
+
const claudeSupport = supportsBedrockClaudePromptCaching(model.id);
|
|
627
|
+
if (claudeSupport !== undefined) return claudeSupport;
|
|
623
628
|
if (model.cost.cacheRead || model.cost.cacheWrite) return true;
|
|
624
|
-
const id = model.id.toLowerCase();
|
|
625
|
-
// Anthropic model 4.x models (opus-4, sonnet-4, haiku-4)
|
|
626
|
-
if (id.includes("claude") && (id.includes("-4-") || id.includes("-4."))) return true;
|
|
627
|
-
// Anthropic model 3.5 Haiku, Anthropic model 3.7 Sonnet (legacy naming)
|
|
628
|
-
if (id.includes("claude-3-7-sonnet") || id.includes("claude-3-5-haiku")) return true;
|
|
629
|
-
// Anthropic model Haiku 4.5+ (new naming)
|
|
630
|
-
if (id.includes("claude-haiku")) return true;
|
|
631
629
|
// Application inference profiles don't contain the model name in the ARN.
|
|
632
630
|
// Allow users to force cache points via environment variable.
|
|
633
631
|
if (typeof process !== "undefined" && $flag("AWS_BEDROCK_FORCE_CACHE")) return true;
|
|
634
632
|
return false;
|
|
635
633
|
}
|
|
636
634
|
|
|
635
|
+
export { parseBedrockClaudeGeneration };
|
|
636
|
+
|
|
637
637
|
/**
|
|
638
638
|
* Check if the model supports thinking signatures in reasoningContent.
|
|
639
639
|
* Only Anthropic Anthropic model models support the signature field.
|
|
@@ -203,6 +203,7 @@ export type AnthropicClientOptionsArgs = {
|
|
|
203
203
|
maxRetryDelayMs?: number;
|
|
204
204
|
streamFirstEventTimeoutMs?: number;
|
|
205
205
|
streamIdleTimeoutMs?: number;
|
|
206
|
+
providerSessionId?: string;
|
|
206
207
|
};
|
|
207
208
|
export type AnthropicClientOptionsResult = {
|
|
208
209
|
isOAuthToken: boolean;
|
|
@@ -111,6 +111,11 @@ import {
|
|
|
111
111
|
hasCopilotVisionInput,
|
|
112
112
|
resolveGitHubCopilotBaseUrl,
|
|
113
113
|
} from "./github-copilot-headers";
|
|
114
|
+
import {
|
|
115
|
+
applyOpenCodeGoSessionHeader,
|
|
116
|
+
resolveOpenCodeGoSessionId,
|
|
117
|
+
wrapFetchForOpenCodeGoSession,
|
|
118
|
+
} from "./opencode-go-session";
|
|
114
119
|
import { hasAdjacentPrivateThinkingBlocks, transformMessages } from "./transform-messages";
|
|
115
120
|
import { NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
|
|
116
121
|
|
|
@@ -1126,6 +1131,7 @@ export type AnthropicClientOptionsArgs = {
|
|
|
1126
1131
|
maxRetryDelayMs?: number;
|
|
1127
1132
|
streamFirstEventTimeoutMs?: number;
|
|
1128
1133
|
streamIdleTimeoutMs?: number;
|
|
1134
|
+
providerSessionId?: string;
|
|
1129
1135
|
};
|
|
1130
1136
|
|
|
1131
1137
|
export type AnthropicClientOptionsResult = {
|
|
@@ -1940,6 +1946,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1940
1946
|
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
1941
1947
|
streamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,
|
|
1942
1948
|
streamIdleTimeoutMs: options?.streamIdleTimeoutMs,
|
|
1949
|
+
providerSessionId: options?.providerSessionId,
|
|
1943
1950
|
});
|
|
1944
1951
|
client = created.client;
|
|
1945
1952
|
isOAuthToken = created.isOAuthToken;
|
|
@@ -3107,7 +3114,11 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
3107
3114
|
const tlsFetchOptions = buildClaudeCodeTlsFetchOptions(model, baseUrl);
|
|
3108
3115
|
const baseFetch = args.fetch ?? fetch;
|
|
3109
3116
|
const boundedFetch = wrapAnthropicFetchForBoundedRateLimits(baseFetch, args.maxRetryDelayMs);
|
|
3110
|
-
const
|
|
3117
|
+
const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, args.providerSessionId, "anthropic");
|
|
3118
|
+
const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
|
|
3119
|
+
const debugFetch = onSseEvent
|
|
3120
|
+
? wrapFetchForSseDebug(providerScopedFetch, event => onSseEvent(event, model))
|
|
3121
|
+
: providerScopedFetch;
|
|
3111
3122
|
// Bound the connect/headers phase. The first-event watchdog arms only after
|
|
3112
3123
|
// response headers arrive, so a request whose connection dies before headers
|
|
3113
3124
|
// was previously governed only by the Anthropic SDK's 10-minute default per
|
|
@@ -3124,16 +3135,19 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
3124
3135
|
if (needsFineGrainedToolStreamingBeta) {
|
|
3125
3136
|
betaFeatures.push(fineGrainedToolStreamingBeta);
|
|
3126
3137
|
}
|
|
3127
|
-
const defaultHeaders =
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3138
|
+
const defaultHeaders = applyOpenCodeGoSessionHeader(
|
|
3139
|
+
mergeHeaders(
|
|
3140
|
+
{
|
|
3141
|
+
Accept: stream ? "text/event-stream" : "application/json",
|
|
3142
|
+
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
|
3143
|
+
Authorization: `Bearer ${copilotApiKey}`,
|
|
3144
|
+
...(betaFeatures.length > 0 ? { "anthropic-beta": buildBetaHeader([], betaFeatures) } : {}),
|
|
3145
|
+
},
|
|
3146
|
+
model.headers,
|
|
3147
|
+
dynamicHeaders,
|
|
3148
|
+
headers,
|
|
3149
|
+
),
|
|
3150
|
+
openCodeGoSessionId,
|
|
3137
3151
|
);
|
|
3138
3152
|
|
|
3139
3153
|
return {
|
|
@@ -3159,16 +3173,19 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
3159
3173
|
betaFeatures.push(interleavedThinkingBeta);
|
|
3160
3174
|
}
|
|
3161
3175
|
|
|
3162
|
-
const defaultHeaders =
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3176
|
+
const defaultHeaders = applyOpenCodeGoSessionHeader(
|
|
3177
|
+
buildAnthropicHeaders({
|
|
3178
|
+
apiKey,
|
|
3179
|
+
baseUrl,
|
|
3180
|
+
isOAuth: oauthToken,
|
|
3181
|
+
extraBetas: betaFeatures,
|
|
3182
|
+
stream,
|
|
3183
|
+
modelHeaders: mergeHeaders(model.headers, foundryCustomHeaders, headers, dynamicHeaders),
|
|
3184
|
+
isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
|
|
3185
|
+
zcodeSourceHeaders: model.provider === "glm-zcode",
|
|
3186
|
+
}),
|
|
3187
|
+
openCodeGoSessionId,
|
|
3188
|
+
);
|
|
3172
3189
|
|
|
3173
3190
|
if (model.provider === "cloudflare-ai-gateway") {
|
|
3174
3191
|
return {
|
package/src/providers/cursor.ts
CHANGED
|
@@ -689,7 +689,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
689
689
|
conversationUsageContextCache.set(conversationId, usageContext);
|
|
690
690
|
const reusableCachedState =
|
|
691
691
|
cachedState && canReuseCursorUsageContext(previousUsageContext, usageContext) ? cachedState : undefined;
|
|
692
|
-
const { requestBytes, conversationState } = buildGrpcRequest(model, context, options, {
|
|
692
|
+
const { requestBytes, conversationState } = await buildGrpcRequest(model, context, options, {
|
|
693
693
|
conversationId,
|
|
694
694
|
blobStore,
|
|
695
695
|
conversationState: reusableCachedState,
|
|
@@ -3459,7 +3459,7 @@ function extractImages(content: (TextContent | ImageContent)[]) {
|
|
|
3459
3459
|
);
|
|
3460
3460
|
}
|
|
3461
3461
|
|
|
3462
|
-
function buildGrpcRequest(
|
|
3462
|
+
async function buildGrpcRequest(
|
|
3463
3463
|
model: Model<"cursor-agent">,
|
|
3464
3464
|
context: Context,
|
|
3465
3465
|
options: CursorOptions | undefined,
|
|
@@ -3468,11 +3468,11 @@ function buildGrpcRequest(
|
|
|
3468
3468
|
blobStore: Map<string, Uint8Array>;
|
|
3469
3469
|
conversationState?: ConversationStateStructure;
|
|
3470
3470
|
},
|
|
3471
|
-
): {
|
|
3471
|
+
): Promise<{
|
|
3472
3472
|
requestBytes: Uint8Array;
|
|
3473
3473
|
blobStore: Map<string, Uint8Array>;
|
|
3474
3474
|
conversationState: ConversationStateStructure;
|
|
3475
|
-
} {
|
|
3475
|
+
}> {
|
|
3476
3476
|
const blobStore = state.blobStore;
|
|
3477
3477
|
|
|
3478
3478
|
const systemPromptIds = buildCursorSystemPromptJsons(context.systemPrompt, model.id).map(json =>
|
|
@@ -3558,7 +3558,7 @@ function buildGrpcRequest(
|
|
|
3558
3558
|
displayName: model.name,
|
|
3559
3559
|
});
|
|
3560
3560
|
|
|
3561
|
-
|
|
3561
|
+
let runRequest = create(AgentRunRequestSchema, {
|
|
3562
3562
|
conversationState,
|
|
3563
3563
|
action,
|
|
3564
3564
|
modelDetails,
|
|
@@ -3573,7 +3573,13 @@ function buildGrpcRequest(
|
|
|
3573
3573
|
: {}),
|
|
3574
3574
|
});
|
|
3575
3575
|
|
|
3576
|
-
options?.onPayload
|
|
3576
|
+
if (options?.onPayload) {
|
|
3577
|
+
const payload = toJson(AgentRunRequestSchema, runRequest);
|
|
3578
|
+
const replacement = await options.onPayload(payload, model, options.attemptScope);
|
|
3579
|
+
if (replacement !== undefined) {
|
|
3580
|
+
runRequest = fromJson(AgentRunRequestSchema, replacement as JsonValue);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3577
3583
|
|
|
3578
3584
|
// Tools are sent later via requestContext (exec handshake)
|
|
3579
3585
|
|
|
@@ -25,7 +25,7 @@ const REQUEST_BLOCKED_MESSAGE_RE = /^\s*request blocked\b/i;
|
|
|
25
25
|
|
|
26
26
|
export async function parseCodexError(response: Response): Promise<CodexErrorInfo> {
|
|
27
27
|
const raw = await response.text();
|
|
28
|
-
let message = raw || response.statusText ||
|
|
28
|
+
let message = raw || response.statusText || `Codex request failed (HTTP ${response.status})`;
|
|
29
29
|
let friendlyMessage: string | undefined;
|
|
30
30
|
let rateLimits: CodexRateLimits | undefined;
|
|
31
31
|
let code: string | undefined;
|
|
@@ -51,6 +51,10 @@ import {
|
|
|
51
51
|
normalizeSystemPrompts,
|
|
52
52
|
sanitizeOpenAIResponsesHistoryItemsForReplay,
|
|
53
53
|
} from "../utils";
|
|
54
|
+
import {
|
|
55
|
+
formatOpenAICodexChatGPTEntitlementError,
|
|
56
|
+
isOpenAICodexChatGPTEntitlementError,
|
|
57
|
+
} from "../utils/codex-entitlement";
|
|
54
58
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
55
59
|
import { STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE, transportFailureFacts } from "../utils/fallback-transport";
|
|
56
60
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
@@ -1234,7 +1238,7 @@ function handleCodexStreamEvent(args: {
|
|
|
1234
1238
|
}
|
|
1235
1239
|
|
|
1236
1240
|
if (eventType === "error" || eventType === "response.failed") {
|
|
1237
|
-
throw createCodexProviderStreamError(rawEvent);
|
|
1241
|
+
throw createCodexProviderStreamError(rawEvent, model.id);
|
|
1238
1242
|
}
|
|
1239
1243
|
|
|
1240
1244
|
return firstTokenTime;
|
|
@@ -2853,7 +2857,11 @@ async function openCodexSseEventStream(
|
|
|
2853
2857
|
updateCodexSessionMetadataFromHeaders(state, response.headers);
|
|
2854
2858
|
if (!response.ok) {
|
|
2855
2859
|
const info = await parseCodexError(response);
|
|
2856
|
-
const error = new Error(
|
|
2860
|
+
const error = new Error(
|
|
2861
|
+
isOpenAICodexChatGPTEntitlementError(info.message, info.code)
|
|
2862
|
+
? formatOpenAICodexChatGPTEntitlementError(body.model)
|
|
2863
|
+
: info.friendlyMessage || info.message,
|
|
2864
|
+
);
|
|
2857
2865
|
(error as { headers?: Headers; status?: number }).headers = response.headers;
|
|
2858
2866
|
(error as { headers?: Headers; status?: number }).status = response.status;
|
|
2859
2867
|
(error as { code?: string }).code = info.code;
|
|
@@ -3235,11 +3243,12 @@ function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolea
|
|
|
3235
3243
|
return !!message && CODEX_RETRYABLE_EVENT_MESSAGE.test(message);
|
|
3236
3244
|
}
|
|
3237
3245
|
|
|
3238
|
-
function createCodexProviderStreamError(rawEvent: Record<string, unknown
|
|
3246
|
+
function createCodexProviderStreamError(rawEvent: Record<string, unknown>, modelId: string): CodexProviderStreamError {
|
|
3239
3247
|
const code = getCodexEventErrorCode(rawEvent);
|
|
3240
3248
|
const message = getCodexEventErrorMessage(rawEvent);
|
|
3241
|
-
const formattedMessage =
|
|
3242
|
-
|
|
3249
|
+
const formattedMessage = isOpenAICodexChatGPTEntitlementError(message, code)
|
|
3250
|
+
? formatOpenAICodexChatGPTEntitlementError(modelId)
|
|
3251
|
+
: typeof rawEvent.type === "string" && rawEvent.type === "error"
|
|
3243
3252
|
? formatCodexErrorEvent(rawEvent, code, message)
|
|
3244
3253
|
: (formatCodexFailure(rawEvent) ?? "Codex response failed");
|
|
3245
3254
|
return new CodexProviderStreamError(
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* the core-safe module so model metadata can use it without loading provider
|
|
4
4
|
* implementations during startup.
|
|
5
5
|
*/
|
|
6
|
-
export { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "../openai-completions-compat";
|
|
6
|
+
export { detectOpenAICompat, type GrokGeneration, isDirectXaiReasoningEffortModel, parseDirectXaiReasoningEffortGeneration, type ResolvedOpenAICompat, resolveOpenAICompat, } from "../openai-completions-compat";
|
|
@@ -3,4 +3,11 @@
|
|
|
3
3
|
* the core-safe module so model metadata can use it without loading provider
|
|
4
4
|
* implementations during startup.
|
|
5
5
|
*/
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
detectOpenAICompat,
|
|
8
|
+
type GrokGeneration,
|
|
9
|
+
isDirectXaiReasoningEffortModel,
|
|
10
|
+
parseDirectXaiReasoningEffortGeneration,
|
|
11
|
+
type ResolvedOpenAICompat,
|
|
12
|
+
resolveOpenAICompat,
|
|
13
|
+
} from "../openai-completions-compat";
|
|
@@ -100,6 +100,11 @@ import {
|
|
|
100
100
|
wrapFetchForOpenAIRequestTransform,
|
|
101
101
|
} from "./openai-request-transform";
|
|
102
102
|
import { createInitialResponsesAssistantMessage } from "./openai-responses-shared";
|
|
103
|
+
import {
|
|
104
|
+
applyOpenCodeGoSessionHeader,
|
|
105
|
+
resolveOpenCodeGoSessionId,
|
|
106
|
+
wrapFetchForOpenCodeGoSession,
|
|
107
|
+
} from "./opencode-go-session";
|
|
103
108
|
import { transformMessages } from "./transform-messages";
|
|
104
109
|
import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
|
|
105
110
|
|
|
@@ -602,6 +607,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
602
607
|
options?.authCredentialType,
|
|
603
608
|
options?.requestMaxRetries,
|
|
604
609
|
options?.sessionId,
|
|
610
|
+
options?.providerSessionId,
|
|
605
611
|
options?.maxRetryDelayMs,
|
|
606
612
|
options?.attemptScope,
|
|
607
613
|
);
|
|
@@ -1259,6 +1265,7 @@ async function createClient(
|
|
|
1259
1265
|
authCredentialType?: OpenAICompletionsOptions["authCredentialType"],
|
|
1260
1266
|
requestMaxRetries?: number,
|
|
1261
1267
|
sessionId?: string,
|
|
1268
|
+
providerSessionId?: string,
|
|
1262
1269
|
maxRetryDelayMs?: number,
|
|
1263
1270
|
attemptScope?: import("../types.js").AttemptScopeRef,
|
|
1264
1271
|
): Promise<{
|
|
@@ -1356,6 +1363,11 @@ async function createClient(
|
|
|
1356
1363
|
const endpointRequestQuery = endpointQuery;
|
|
1357
1364
|
const requestQuery =
|
|
1358
1365
|
[endpointRequestQuery, azureQuery].filter((query): query is string => query !== undefined).join("&") || undefined;
|
|
1366
|
+
const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, providerSessionId, "openai");
|
|
1367
|
+
// Reserve the provider-specific header on every OpenAI-compatible route. Caller,
|
|
1368
|
+
// model, and transform values are removed unless the exact OpenCode Go endpoint
|
|
1369
|
+
// has an opaque identity owned by the agent's conversation lifecycle.
|
|
1370
|
+
headers = applyOpenCodeGoSessionHeader(headers, openCodeGoSessionId);
|
|
1359
1371
|
let capturedErrorResponse: CapturedHttpErrorResponse | undefined;
|
|
1360
1372
|
const baseFetch = fetchOverride ?? fetch;
|
|
1361
1373
|
const wrappedFetch = Object.assign(
|
|
@@ -1389,8 +1401,9 @@ async function createClient(
|
|
|
1389
1401
|
baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
|
|
1390
1402
|
);
|
|
1391
1403
|
const boundedFetch = wrapOpenAIFetchForBoundedRateLimits(wrappedFetch, maxRetryDelayMs);
|
|
1404
|
+
const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
|
|
1392
1405
|
const transformedFetch = wrapFetchForOpenAIRequestTransform(
|
|
1393
|
-
|
|
1406
|
+
providerScopedFetch,
|
|
1394
1407
|
model.requestTransform,
|
|
1395
1408
|
`Gajae-Code/${packageJson.version}`,
|
|
1396
1409
|
);
|
|
@@ -93,6 +93,11 @@ import {
|
|
|
93
93
|
processResponsesStream,
|
|
94
94
|
repairOrphanResponsesToolOutputs,
|
|
95
95
|
} from "./openai-responses-shared";
|
|
96
|
+
import {
|
|
97
|
+
applyOpenCodeGoSessionHeader,
|
|
98
|
+
resolveOpenCodeGoSessionId,
|
|
99
|
+
wrapFetchForOpenCodeGoSession,
|
|
100
|
+
} from "./opencode-go-session";
|
|
96
101
|
import { transformMessages } from "./transform-messages";
|
|
97
102
|
|
|
98
103
|
/**
|
|
@@ -378,6 +383,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
378
383
|
options?.headers,
|
|
379
384
|
options?.initiatorOverride,
|
|
380
385
|
cacheSessionId,
|
|
386
|
+
options?.providerSessionId,
|
|
381
387
|
cacheRetention,
|
|
382
388
|
options?.onSseEvent,
|
|
383
389
|
options?.fetch,
|
|
@@ -556,6 +562,7 @@ function createClient(
|
|
|
556
562
|
extraHeaders?: Record<string, string>,
|
|
557
563
|
initiatorOverride?: MessageAttribution,
|
|
558
564
|
sessionId?: string,
|
|
565
|
+
providerSessionId?: string,
|
|
559
566
|
cacheRetention?: CacheRetention,
|
|
560
567
|
onSseEvent?: OpenAIResponsesOptions["onSseEvent"],
|
|
561
568
|
fetchOverride?: FetchImpl,
|
|
@@ -618,6 +625,8 @@ function createClient(
|
|
|
618
625
|
}
|
|
619
626
|
headers = applyOpenAIRequestTransformHeaders(headers, model.requestTransform, `Gajae-Code/${packageJson.version}`);
|
|
620
627
|
const { baseUrl: clientBaseUrl, query: endpointQuery } = splitBaseUrlQuery(baseUrl);
|
|
628
|
+
const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, providerSessionId, "openai");
|
|
629
|
+
headers = applyOpenCodeGoSessionHeader(headers, openCodeGoSessionId);
|
|
621
630
|
const baseFetch = fetchOverride ?? fetch;
|
|
622
631
|
const queryFetch = Object.assign(
|
|
623
632
|
async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
@@ -626,8 +635,9 @@ function createClient(
|
|
|
626
635
|
baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
|
|
627
636
|
);
|
|
628
637
|
const boundedFetch = wrapOpenAIFetchForBoundedRateLimits(queryFetch, maxRetryDelayMs);
|
|
638
|
+
const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
|
|
629
639
|
const transformedFetch = wrapFetchForOpenAIRequestTransform(
|
|
630
|
-
|
|
640
|
+
providerScopedFetch,
|
|
631
641
|
model.requestTransform,
|
|
632
642
|
`Gajae-Code/${packageJson.version}`,
|
|
633
643
|
);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FetchImpl, Model } from "../types";
|
|
2
|
+
export type OpenCodeGoApiFamily = "openai" | "anthropic";
|
|
3
|
+
export declare function resolveOpenCodeGoSessionId(model: Pick<Model, "provider">, baseUrl: string | undefined, providerSessionId: string | undefined, apiFamily: OpenCodeGoApiFamily): string | undefined;
|
|
4
|
+
export declare function applyOpenCodeGoSessionHeader(headers: Record<string, string>, sessionId: string | undefined): Record<string, string>;
|
|
5
|
+
export declare function wrapFetchForOpenCodeGoSession(baseFetch: FetchImpl, sessionId: string | undefined): FetchImpl;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { FetchImpl, Model } from "../types";
|
|
2
|
+
|
|
3
|
+
const OPENCODE_GO_ORIGIN = "https://opencode.ai";
|
|
4
|
+
const OPENCODE_GO_OPENAI_BASE_PATH = "/zen/go/v1";
|
|
5
|
+
const OPENCODE_GO_ANTHROPIC_BASE_PATH = "/zen/go";
|
|
6
|
+
|
|
7
|
+
export type OpenCodeGoApiFamily = "openai" | "anthropic";
|
|
8
|
+
|
|
9
|
+
function expectedBasePath(apiFamily: OpenCodeGoApiFamily): string {
|
|
10
|
+
return apiFamily === "openai" ? OPENCODE_GO_OPENAI_BASE_PATH : OPENCODE_GO_ANTHROPIC_BASE_PATH;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveOpenCodeGoSessionId(
|
|
14
|
+
model: Pick<Model, "provider">,
|
|
15
|
+
baseUrl: string | undefined,
|
|
16
|
+
providerSessionId: string | undefined,
|
|
17
|
+
apiFamily: OpenCodeGoApiFamily,
|
|
18
|
+
): string | undefined {
|
|
19
|
+
if (model.provider !== "opencode-go" || !baseUrl || !providerSessionId) return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(baseUrl);
|
|
22
|
+
if (url.origin !== OPENCODE_GO_ORIGIN) return undefined;
|
|
23
|
+
if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") return undefined;
|
|
24
|
+
if (url.pathname.replace(/\/+$/u, "") !== expectedBasePath(apiFamily)) return undefined;
|
|
25
|
+
return providerSessionId;
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function applyOpenCodeGoSessionHeader(
|
|
32
|
+
headers: Record<string, string>,
|
|
33
|
+
sessionId: string | undefined,
|
|
34
|
+
): Record<string, string> {
|
|
35
|
+
const normalizedHeaders = new Headers(headers);
|
|
36
|
+
normalizedHeaders.delete("x-opencode-session");
|
|
37
|
+
if (sessionId) normalizedHeaders.set("x-opencode-session", sessionId);
|
|
38
|
+
return Object.fromEntries(normalizedHeaders.entries());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function wrapFetchForOpenCodeGoSession(baseFetch: FetchImpl, sessionId: string | undefined): FetchImpl {
|
|
42
|
+
return Object.assign(
|
|
43
|
+
async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
|
44
|
+
if (input instanceof Request) {
|
|
45
|
+
const request = new Request(input, init);
|
|
46
|
+
request.headers.delete("x-opencode-session");
|
|
47
|
+
if (sessionId) request.headers.set("x-opencode-session", sessionId);
|
|
48
|
+
return baseFetch(request);
|
|
49
|
+
}
|
|
50
|
+
const headers = new Headers(init?.headers);
|
|
51
|
+
headers.delete("x-opencode-session");
|
|
52
|
+
if (sessionId) headers.set("x-opencode-session", sessionId);
|
|
53
|
+
return baseFetch(input, { ...init, headers });
|
|
54
|
+
},
|
|
55
|
+
baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
|
|
56
|
+
);
|
|
57
|
+
}
|
package/src/stream.ts
CHANGED
|
@@ -955,6 +955,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
955
955
|
streamMaxRetries: options?.fallbackManaged ? 0 : options?.streamMaxRetries,
|
|
956
956
|
metadata: options?.metadata,
|
|
957
957
|
sessionId: options?.sessionId,
|
|
958
|
+
providerSessionId: options?.providerSessionId,
|
|
958
959
|
providerSessionState: options?.providerSessionState,
|
|
959
960
|
onPayload: options?.onPayload,
|
|
960
961
|
onResponse: options?.onResponse,
|
package/src/types.d.ts
CHANGED
|
@@ -239,6 +239,13 @@ export interface StreamOptions {
|
|
|
239
239
|
* session-aware features. Ignored by providers that don't support it.
|
|
240
240
|
*/
|
|
241
241
|
sessionId?: string;
|
|
242
|
+
/**
|
|
243
|
+
* Opaque conversation identity owned by the calling agent/session lifecycle.
|
|
244
|
+
* Unlike `sessionId`, this MUST NOT be synthesized from prompts, credentials,
|
|
245
|
+
* paths, cache keys, or other request content. Providers with a dedicated
|
|
246
|
+
* conversation header may use this only when their endpoint policy permits it.
|
|
247
|
+
*/
|
|
248
|
+
providerSessionId?: string;
|
|
242
249
|
/**
|
|
243
250
|
* Provider-scoped mutable state store for this agent session.
|
|
244
251
|
* Providers can use this to persist transport/session state between turns.
|
package/src/types.ts
CHANGED
|
@@ -443,6 +443,13 @@ export interface StreamOptions {
|
|
|
443
443
|
* session-aware features. Ignored by providers that don't support it.
|
|
444
444
|
*/
|
|
445
445
|
sessionId?: string;
|
|
446
|
+
/**
|
|
447
|
+
* Opaque conversation identity owned by the calling agent/session lifecycle.
|
|
448
|
+
* Unlike `sessionId`, this MUST NOT be synthesized from prompts, credentials,
|
|
449
|
+
* paths, cache keys, or other request content. Providers with a dedicated
|
|
450
|
+
* conversation header may use this only when their endpoint policy permits it.
|
|
451
|
+
*/
|
|
452
|
+
providerSessionId?: string;
|
|
446
453
|
/**
|
|
447
454
|
* Provider-scoped mutable state store for this agent session.
|
|
448
455
|
* Providers can use this to persist transport/session state between turns.
|