@gajae-code/ai 0.17.2 → 0.17.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 +103 -0
- package/dist/types/auth-gateway/server.d.ts +23 -1
- package/dist/types/auth-storage.d.ts +12 -1
- package/dist/types/providers/anthropic.d.ts +1 -1
- package/dist/types/providers/cursor.d.ts +10 -0
- package/dist/types/providers/openai-completions.d.ts +9 -1
- package/dist/types/types.d.ts +16 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +10 -0
- package/dist/types/utils/fallback-transport.d.ts +4 -0
- package/dist/types/utils/h2-fetch.d.ts +8 -7
- package/dist/types/utils/stream-repetition-guard.d.ts +107 -0
- package/dist/types/utils/tool-call-healing.d.ts +4 -0
- package/dist/types/utils/tool-fence-strip.d.ts +27 -0
- package/package.json +3 -3
- package/src/auth-gateway/server.ts +48 -9
- package/src/auth-storage.ts +185 -48
- package/src/model-pricing.ts +22 -0
- package/src/model-thinking.ts +40 -4
- package/src/models.json +191 -15
- package/src/providers/anthropic.d.ts +1 -1
- package/src/providers/anthropic.ts +1 -1
- package/src/providers/cursor.d.ts +10 -0
- package/src/providers/cursor.ts +144 -24
- package/src/providers/openai-completions.d.ts +9 -1
- package/src/providers/openai-completions.ts +371 -126
- package/src/stream.ts +7 -0
- package/src/types.d.ts +16 -0
- package/src/types.ts +17 -0
- package/src/utils/discovery/openai-compatible.ts +16 -2
- package/src/utils/fallback-transport.d.ts +4 -0
- package/src/utils/fallback-transport.ts +11 -0
- package/src/utils/h2-fetch.ts +65 -26
- package/src/utils/http-inspector.ts +2 -0
- package/src/utils/idle-iterator.ts +109 -96
- package/src/utils/json-parse.ts +12 -4
- package/src/utils/stream-repetition-guard.d.ts +107 -0
- package/src/utils/stream-repetition-guard.ts +290 -0
- package/src/utils/tool-call-healing.d.ts +4 -0
- package/src/utils/tool-call-healing.ts +4 -0
- package/src/utils/tool-fence-strip.d.ts +27 -0
- package/src/utils/tool-fence-strip.ts +64 -0
package/src/models.json
CHANGED
|
@@ -116,6 +116,76 @@
|
|
|
116
116
|
"maxLevel": "xhigh"
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
|
+
"deepseek-v4-pro-0813": {
|
|
120
|
+
"id": "deepseek-v4-pro-0813",
|
|
121
|
+
"name": "DeepSeek V4 Pro 0813",
|
|
122
|
+
"api": "openai-completions",
|
|
123
|
+
"provider": "alibaba-token-plan",
|
|
124
|
+
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
125
|
+
"reasoning": true,
|
|
126
|
+
"input": [
|
|
127
|
+
"text"
|
|
128
|
+
],
|
|
129
|
+
"cost": {
|
|
130
|
+
"input": 0,
|
|
131
|
+
"output": 0,
|
|
132
|
+
"cacheRead": 0,
|
|
133
|
+
"cacheWrite": 0
|
|
134
|
+
},
|
|
135
|
+
"contextWindow": 1000000,
|
|
136
|
+
"maxTokens": 384000,
|
|
137
|
+
"compat": {
|
|
138
|
+
"supportsDeveloperRole": false,
|
|
139
|
+
"supportsReasoningEffort": true,
|
|
140
|
+
"reasoningContentField": "reasoning_content",
|
|
141
|
+
"requiresReasoningContentForToolCalls": true
|
|
142
|
+
},
|
|
143
|
+
"thinking": {
|
|
144
|
+
"mode": "effort",
|
|
145
|
+
"minLevel": "low",
|
|
146
|
+
"maxLevel": "max",
|
|
147
|
+
"levels": [
|
|
148
|
+
"low",
|
|
149
|
+
"high",
|
|
150
|
+
"max"
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"deepseek-v4.1-flash": {
|
|
155
|
+
"id": "deepseek-v4.1-flash",
|
|
156
|
+
"name": "DeepSeek V4.1 Flash",
|
|
157
|
+
"api": "openai-completions",
|
|
158
|
+
"provider": "alibaba-token-plan",
|
|
159
|
+
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
160
|
+
"reasoning": true,
|
|
161
|
+
"input": [
|
|
162
|
+
"text"
|
|
163
|
+
],
|
|
164
|
+
"cost": {
|
|
165
|
+
"input": 0,
|
|
166
|
+
"output": 0,
|
|
167
|
+
"cacheRead": 0,
|
|
168
|
+
"cacheWrite": 0
|
|
169
|
+
},
|
|
170
|
+
"contextWindow": 1000000,
|
|
171
|
+
"maxTokens": 384000,
|
|
172
|
+
"compat": {
|
|
173
|
+
"supportsDeveloperRole": false,
|
|
174
|
+
"supportsReasoningEffort": true,
|
|
175
|
+
"reasoningContentField": "reasoning_content",
|
|
176
|
+
"requiresReasoningContentForToolCalls": true
|
|
177
|
+
},
|
|
178
|
+
"thinking": {
|
|
179
|
+
"mode": "effort",
|
|
180
|
+
"minLevel": "low",
|
|
181
|
+
"maxLevel": "max",
|
|
182
|
+
"levels": [
|
|
183
|
+
"low",
|
|
184
|
+
"high",
|
|
185
|
+
"max"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
},
|
|
119
189
|
"glm-5": {
|
|
120
190
|
"id": "glm-5",
|
|
121
191
|
"name": "GLM-5",
|
|
@@ -197,6 +267,42 @@
|
|
|
197
267
|
"maxLevel": "xhigh"
|
|
198
268
|
}
|
|
199
269
|
},
|
|
270
|
+
"glm-5.3": {
|
|
271
|
+
"id": "glm-5.3",
|
|
272
|
+
"name": "GLM-5.3",
|
|
273
|
+
"api": "openai-completions",
|
|
274
|
+
"provider": "alibaba-token-plan",
|
|
275
|
+
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
276
|
+
"reasoning": true,
|
|
277
|
+
"input": [
|
|
278
|
+
"text"
|
|
279
|
+
],
|
|
280
|
+
"cost": {
|
|
281
|
+
"input": 0,
|
|
282
|
+
"output": 0,
|
|
283
|
+
"cacheRead": 0,
|
|
284
|
+
"cacheWrite": 0
|
|
285
|
+
},
|
|
286
|
+
"contextWindow": 1000000,
|
|
287
|
+
"maxTokens": 131072,
|
|
288
|
+
"compat": {
|
|
289
|
+
"supportsDeveloperRole": false,
|
|
290
|
+
"supportsReasoningEffort": true,
|
|
291
|
+
"reasoningContentField": "reasoning_content",
|
|
292
|
+
"requiresReasoningContentForToolCalls": true
|
|
293
|
+
},
|
|
294
|
+
"thinking": {
|
|
295
|
+
"mode": "effort",
|
|
296
|
+
"minLevel": "low",
|
|
297
|
+
"maxLevel": "max",
|
|
298
|
+
"defaultLevel": "max",
|
|
299
|
+
"levels": [
|
|
300
|
+
"low",
|
|
301
|
+
"high",
|
|
302
|
+
"max"
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
},
|
|
200
306
|
"kimi-k2.5": {
|
|
201
307
|
"id": "kimi-k2.5",
|
|
202
308
|
"name": "Kimi K2.5",
|
|
@@ -46019,9 +46125,9 @@
|
|
|
46019
46125
|
"contextWindow": 1000000,
|
|
46020
46126
|
"maxTokens": 128000,
|
|
46021
46127
|
"thinking": {
|
|
46022
|
-
"mode": "
|
|
46023
|
-
"minLevel": "
|
|
46024
|
-
"maxLevel": "
|
|
46128
|
+
"mode": "effort",
|
|
46129
|
+
"minLevel": "high",
|
|
46130
|
+
"maxLevel": "high"
|
|
46025
46131
|
}
|
|
46026
46132
|
},
|
|
46027
46133
|
"MiniMax-M3[1m]": {
|
|
@@ -46044,9 +46150,9 @@
|
|
|
46044
46150
|
"contextWindow": 1000000,
|
|
46045
46151
|
"maxTokens": 128000,
|
|
46046
46152
|
"thinking": {
|
|
46047
|
-
"mode": "
|
|
46048
|
-
"minLevel": "
|
|
46049
|
-
"maxLevel": "
|
|
46153
|
+
"mode": "effort",
|
|
46154
|
+
"minLevel": "high",
|
|
46155
|
+
"maxLevel": "high"
|
|
46050
46156
|
}
|
|
46051
46157
|
}
|
|
46052
46158
|
},
|
|
@@ -46239,9 +46345,9 @@
|
|
|
46239
46345
|
"contextWindow": 1000000,
|
|
46240
46346
|
"maxTokens": 128000,
|
|
46241
46347
|
"thinking": {
|
|
46242
|
-
"mode": "
|
|
46243
|
-
"minLevel": "
|
|
46244
|
-
"maxLevel": "
|
|
46348
|
+
"mode": "effort",
|
|
46349
|
+
"minLevel": "high",
|
|
46350
|
+
"maxLevel": "high"
|
|
46245
46351
|
}
|
|
46246
46352
|
},
|
|
46247
46353
|
"MiniMax-M3[1m]": {
|
|
@@ -46264,9 +46370,9 @@
|
|
|
46264
46370
|
"contextWindow": 1000000,
|
|
46265
46371
|
"maxTokens": 128000,
|
|
46266
46372
|
"thinking": {
|
|
46267
|
-
"mode": "
|
|
46268
|
-
"minLevel": "
|
|
46269
|
-
"maxLevel": "
|
|
46373
|
+
"mode": "effort",
|
|
46374
|
+
"minLevel": "high",
|
|
46375
|
+
"maxLevel": "high"
|
|
46270
46376
|
}
|
|
46271
46377
|
}
|
|
46272
46378
|
},
|
|
@@ -46538,7 +46644,7 @@
|
|
|
46538
46644
|
},
|
|
46539
46645
|
"thinking": {
|
|
46540
46646
|
"mode": "effort",
|
|
46541
|
-
"minLevel": "
|
|
46647
|
+
"minLevel": "high",
|
|
46542
46648
|
"maxLevel": "high"
|
|
46543
46649
|
}
|
|
46544
46650
|
}
|
|
@@ -46811,7 +46917,7 @@
|
|
|
46811
46917
|
},
|
|
46812
46918
|
"thinking": {
|
|
46813
46919
|
"mode": "effort",
|
|
46814
|
-
"minLevel": "
|
|
46920
|
+
"minLevel": "high",
|
|
46815
46921
|
"maxLevel": "high"
|
|
46816
46922
|
}
|
|
46817
46923
|
}
|
|
@@ -65544,6 +65650,76 @@
|
|
|
65544
65650
|
},
|
|
65545
65651
|
"applyPatchToolType": "freeform"
|
|
65546
65652
|
},
|
|
65653
|
+
"gpt-6-luna": {
|
|
65654
|
+
"id": "gpt-6-luna",
|
|
65655
|
+
"name": "GPT-6-Luna",
|
|
65656
|
+
"api": "openai-codex-responses",
|
|
65657
|
+
"provider": "openai-codex",
|
|
65658
|
+
"baseUrl": "https://chatgpt.com/backend-api",
|
|
65659
|
+
"reasoning": true,
|
|
65660
|
+
"input": [
|
|
65661
|
+
"text",
|
|
65662
|
+
"image"
|
|
65663
|
+
],
|
|
65664
|
+
"cost": {
|
|
65665
|
+
"input": 0.1,
|
|
65666
|
+
"output": 0.5,
|
|
65667
|
+
"cacheRead": 0.01,
|
|
65668
|
+
"cacheWrite": 0.125
|
|
65669
|
+
},
|
|
65670
|
+
"contextWindow": 272000,
|
|
65671
|
+
"maxTokens": 128000,
|
|
65672
|
+
"preferWebsockets": true,
|
|
65673
|
+
"thinking": {
|
|
65674
|
+
"mode": "effort",
|
|
65675
|
+
"minLevel": "low",
|
|
65676
|
+
"maxLevel": "max"
|
|
65677
|
+
},
|
|
65678
|
+
"longContextPricing": {
|
|
65679
|
+
"threshold": 272000,
|
|
65680
|
+
"cost": {
|
|
65681
|
+
"input": 0.2,
|
|
65682
|
+
"output": 0.75,
|
|
65683
|
+
"cacheRead": 0.02,
|
|
65684
|
+
"cacheWrite": 0.25
|
|
65685
|
+
}
|
|
65686
|
+
}
|
|
65687
|
+
},
|
|
65688
|
+
"gpt-6-sol": {
|
|
65689
|
+
"id": "gpt-6-sol",
|
|
65690
|
+
"name": "GPT-6-Sol",
|
|
65691
|
+
"api": "openai-codex-responses",
|
|
65692
|
+
"provider": "openai-codex",
|
|
65693
|
+
"baseUrl": "https://chatgpt.com/backend-api",
|
|
65694
|
+
"reasoning": true,
|
|
65695
|
+
"input": [
|
|
65696
|
+
"text",
|
|
65697
|
+
"image"
|
|
65698
|
+
],
|
|
65699
|
+
"cost": {
|
|
65700
|
+
"input": 2,
|
|
65701
|
+
"output": 10,
|
|
65702
|
+
"cacheRead": 0.2,
|
|
65703
|
+
"cacheWrite": 2.5
|
|
65704
|
+
},
|
|
65705
|
+
"contextWindow": 272000,
|
|
65706
|
+
"maxTokens": 128000,
|
|
65707
|
+
"preferWebsockets": true,
|
|
65708
|
+
"thinking": {
|
|
65709
|
+
"mode": "effort",
|
|
65710
|
+
"minLevel": "low",
|
|
65711
|
+
"maxLevel": "max"
|
|
65712
|
+
},
|
|
65713
|
+
"longContextPricing": {
|
|
65714
|
+
"threshold": 272000,
|
|
65715
|
+
"cost": {
|
|
65716
|
+
"input": 4,
|
|
65717
|
+
"output": 15,
|
|
65718
|
+
"cacheRead": 0.4,
|
|
65719
|
+
"cacheWrite": 5
|
|
65720
|
+
}
|
|
65721
|
+
}
|
|
65722
|
+
},
|
|
65547
65723
|
"gpt-daybreak-blue-latest": {
|
|
65548
65724
|
"id": "gpt-daybreak-blue-latest",
|
|
65549
65725
|
"name": "Daybreak Blue",
|
|
@@ -95071,4 +95247,4 @@
|
|
|
95071
95247
|
}
|
|
95072
95248
|
}
|
|
95073
95249
|
}
|
|
95074
|
-
}
|
|
95250
|
+
}
|
|
@@ -107,7 +107,7 @@ export interface CpaToolAliasRestoreFailure {
|
|
|
107
107
|
*/
|
|
108
108
|
export declare function parseCpaToolAliasRestoreFailure(error: unknown): CpaToolAliasRestoreFailure | undefined;
|
|
109
109
|
export declare function isCpaToolAliasRestoreFailure(error: unknown): boolean;
|
|
110
|
-
export declare const claudeCodeVersion = "2.1.
|
|
110
|
+
export declare const claudeCodeVersion = "2.1.280";
|
|
111
111
|
export declare const claudeCodeEntrypoint = "sdk-cli";
|
|
112
112
|
export declare const claudeToolPrefix: string;
|
|
113
113
|
export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
@@ -834,7 +834,7 @@ function getCacheControl(
|
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
// Stealth mode: Mimic Anthropic Code headers and tool prefixing.
|
|
837
|
-
export const claudeCodeVersion = "2.1.
|
|
837
|
+
export const claudeCodeVersion = "2.1.280";
|
|
838
838
|
export const claudeCodeEntrypoint = "sdk-cli";
|
|
839
839
|
export const claudeToolPrefix: string = "proxy_";
|
|
840
840
|
export const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
@@ -73,6 +73,8 @@ export declare function createCursorMessageQueueForTest(onError?: (error: unknow
|
|
|
73
73
|
};
|
|
74
74
|
/** Exported for direct regression coverage of the JSON-safety boundary. */
|
|
75
75
|
export declare function cursorJsonSafeValueForTest(value: unknown): unknown;
|
|
76
|
+
/** Exported for direct regression coverage of the Cursor serialization boundary. */
|
|
77
|
+
export declare function cursorJsonSafeStringifyForTest(value: unknown): string;
|
|
76
78
|
export declare function buildNativeToolCallBlock(toolCall: Record<string, unknown>, callId: string, index: number): ToolCallState | null;
|
|
77
79
|
/** Derive prompt usage from Cursor's whole-conversation checkpoint total. */
|
|
78
80
|
export declare function finalizeCursorUsage(output: AssistantMessage, usageState: UsageState): void;
|
|
@@ -103,6 +105,14 @@ export declare function finalizeCursorUsageForTest(usedTokens: number, outputTok
|
|
|
103
105
|
export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined, modelId?: string): string[];
|
|
104
106
|
/** Exported for regression coverage of the tool usage-cache identity boundary. */
|
|
105
107
|
export declare function buildCursorUsageToolsKeyForTest(tools: Tool[]): string;
|
|
108
|
+
/** Exported for regression coverage of the generic tool-schema wire boundary. */
|
|
109
|
+
export declare function buildCursorWireToolIdentitiesForTest(tools: Tool[]): Array<{
|
|
110
|
+
name: string;
|
|
111
|
+
description: string;
|
|
112
|
+
inputSchema: JsonValue;
|
|
113
|
+
}>;
|
|
114
|
+
/** Exported for regression coverage of lossless conversation identity hashing. */
|
|
115
|
+
export declare function hashCursorConversationValueForTest(value: unknown): string;
|
|
106
116
|
/** Exported for tests: decodes Cursor history blobs built from conversation messages. */
|
|
107
117
|
export declare function buildCursorHistoryForTest(messages: Message[]): {
|
|
108
118
|
rootPromptMessagesJson: unknown[];
|
package/src/providers/cursor.ts
CHANGED
|
@@ -1288,6 +1288,9 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1288
1288
|
let terminalDrainStarted = false;
|
|
1289
1289
|
let execQueuePrefix: Promise<void> | undefined;
|
|
1290
1290
|
let terminalPendingError: unknown;
|
|
1291
|
+
let requestCloseError: (Error & { http2RstCode?: number; nativeErrorCode?: string }) | undefined;
|
|
1292
|
+
// Native errors may be frozen; keep observations separate from their identity.
|
|
1293
|
+
const requestErrorResetCodes = new WeakMap<Error, number | undefined>();
|
|
1291
1294
|
let terminalBoundarySeen = false;
|
|
1292
1295
|
// Lookahead can validate turnEnded while an exec handler holds the normal
|
|
1293
1296
|
// parser. Close new exec admission immediately, but leave the validated
|
|
@@ -1595,6 +1598,12 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1595
1598
|
};
|
|
1596
1599
|
h2RequestErrorHandler = error => {
|
|
1597
1600
|
if (terminalBoundarySeen || terminalBoundaryObserved || sawTurnEnded) return;
|
|
1601
|
+
// Enrich only the synthetic reset diagnostic with the first observed
|
|
1602
|
+
// native code. Never replace its message, priority, or retry class.
|
|
1603
|
+
if (requestCloseError && !requestCloseError.nativeErrorCode) {
|
|
1604
|
+
requestCloseError.nativeErrorCode = transportFailureFacts(error)?.nativeErrorCode;
|
|
1605
|
+
}
|
|
1606
|
+
if (!requestErrorResetCodes.has(error)) requestErrorResetCodes.set(error, h2Request?.rstCode);
|
|
1598
1607
|
terminalize(error, "drainable");
|
|
1599
1608
|
};
|
|
1600
1609
|
h2Request.on("error", h2RequestErrorHandler);
|
|
@@ -1637,7 +1646,10 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1637
1646
|
return;
|
|
1638
1647
|
}
|
|
1639
1648
|
responseEnded = true;
|
|
1640
|
-
|
|
1649
|
+
requestCloseError = Object.assign(new Error(`Cursor HTTP/2 request ${kind} before turnEnded`), {
|
|
1650
|
+
http2RstCode: h2Request?.rstCode,
|
|
1651
|
+
});
|
|
1652
|
+
terminalize(requestCloseError, "drainable");
|
|
1641
1653
|
};
|
|
1642
1654
|
h2RequestCloseHandler = () => handleUnexpectedRequestClose("closed");
|
|
1643
1655
|
h2RequestAbortedHandler = () => handleUnexpectedRequestClose("aborted");
|
|
@@ -2333,6 +2345,15 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
2333
2345
|
output.stopReason = callerAbortError || options?.signal?.aborted ? "aborted" : "error";
|
|
2334
2346
|
output.errorStatus = extractHttpStatusFromError(mappedError);
|
|
2335
2347
|
output.transportFailure = transportFailureFacts(mappedError);
|
|
2348
|
+
if (mappedError instanceof Error && requestErrorResetCodes.has(mappedError)) {
|
|
2349
|
+
output.transportFailure = transportFailureFacts({
|
|
2350
|
+
...output.transportFailure,
|
|
2351
|
+
// The native error can precede the request's reset observation.
|
|
2352
|
+
// Fill a missing observation at settlement, after terminal draining;
|
|
2353
|
+
// local teardown may supply it, so this is not remote-cause evidence.
|
|
2354
|
+
http2RstCode: requestErrorResetCodes.get(mappedError) ?? h2Request?.rstCode,
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2336
2357
|
output.errorMessage = formatErrorMessageWithRetryAfter(mappedError);
|
|
2337
2358
|
finalizeCursorUsage(output, usageState);
|
|
2338
2359
|
calculateCost(model, output.usage);
|
|
@@ -4150,6 +4171,42 @@ function cursorNativeToolName(kindKey: string): string {
|
|
|
4150
4171
|
/** Hard node budget for one native-payload conversion; bounds hostile or cyclic graphs. */
|
|
4151
4172
|
const CURSOR_JSON_SAFE_MAX_NODES = 10_000;
|
|
4152
4173
|
const CURSOR_JSON_SAFE_MAX_DEPTH = 100;
|
|
4174
|
+
/** Generic boundaries remain lossless within explicit resource limits. */
|
|
4175
|
+
const CURSOR_GENERIC_JSON_SAFE_MAX_NODES = 100_000;
|
|
4176
|
+
const CURSOR_GENERIC_JSON_SAFE_MAX_DEPTH = 1_000;
|
|
4177
|
+
|
|
4178
|
+
interface CursorJsonSafeOptions {
|
|
4179
|
+
stripTypeName: boolean;
|
|
4180
|
+
maxNodes?: number;
|
|
4181
|
+
maxDepth?: number;
|
|
4182
|
+
throwOnLimit?: boolean;
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
const CURSOR_NATIVE_JSON_SAFE_OPTIONS: CursorJsonSafeOptions = {
|
|
4186
|
+
stripTypeName: true,
|
|
4187
|
+
maxNodes: CURSOR_JSON_SAFE_MAX_NODES,
|
|
4188
|
+
maxDepth: CURSOR_JSON_SAFE_MAX_DEPTH,
|
|
4189
|
+
};
|
|
4190
|
+
|
|
4191
|
+
/** Generic JSON boundaries must preserve every schema/context entry losslessly. */
|
|
4192
|
+
const CURSOR_GENERIC_JSON_SAFE_OPTIONS: CursorJsonSafeOptions = {
|
|
4193
|
+
stripTypeName: false,
|
|
4194
|
+
maxNodes: CURSOR_GENERIC_JSON_SAFE_MAX_NODES,
|
|
4195
|
+
maxDepth: CURSOR_GENERIC_JSON_SAFE_MAX_DEPTH,
|
|
4196
|
+
throwOnLimit: true,
|
|
4197
|
+
};
|
|
4198
|
+
|
|
4199
|
+
class CursorJsonSafeLimitError extends Error {
|
|
4200
|
+
constructor(message: string) {
|
|
4201
|
+
super(message);
|
|
4202
|
+
this.name = "CursorJsonSafeLimitError";
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
function cursorJsonSafeLimit(options: CursorJsonSafeOptions, message: string): null {
|
|
4207
|
+
if (options.throwOnLimit) throw new CursorJsonSafeLimitError(message);
|
|
4208
|
+
return null;
|
|
4209
|
+
}
|
|
4153
4210
|
|
|
4154
4211
|
/**
|
|
4155
4212
|
* Total conversion of a Cursor protobuf payload into plain JSON-safe data.
|
|
@@ -4162,17 +4219,39 @@ const CURSOR_JSON_SAFE_MAX_DEPTH = 100;
|
|
|
4162
4219
|
* all of which require `JSON.stringify`-safe values. Attaching the raw payload
|
|
4163
4220
|
* is exactly the local-snapshot producer defect class behind issue #4578.
|
|
4164
4221
|
*
|
|
4165
|
-
*
|
|
4166
|
-
* strings beyond `Number.MAX_SAFE_INTEGER`), byte arrays
|
|
4167
|
-
* strings, dates become ISO strings, functions/symbols are
|
|
4168
|
-
* and over-depth values collapse to null, and containers stop
|
|
4169
|
-
* entries once the shared node budget is exhausted.
|
|
4222
|
+
* Native payload rules: `$typeName` is stripped, safe-range bigints become
|
|
4223
|
+
* numbers (decimal strings beyond `Number.MAX_SAFE_INTEGER`), byte arrays
|
|
4224
|
+
* become base64 strings, dates become ISO strings, functions/symbols are
|
|
4225
|
+
* dropped, cycles and over-depth values collapse to null, and containers stop
|
|
4226
|
+
* accepting entries once the shared node budget is exhausted. Generic payload
|
|
4227
|
+
* boundaries use the same conversion with their own explicit limits and reject
|
|
4228
|
+
* limit exhaustion instead of returning a truncated value.
|
|
4170
4229
|
*/
|
|
4171
|
-
function cursorJsonSafeValue(value: unknown
|
|
4230
|
+
function cursorJsonSafeValue(value: unknown): unknown {
|
|
4231
|
+
return cursorJsonSafeValueWithOptions(value, CURSOR_NATIVE_JSON_SAFE_OPTIONS);
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
function cursorJsonSafeValueWithOptions(
|
|
4235
|
+
value: unknown,
|
|
4236
|
+
options: CursorJsonSafeOptions,
|
|
4237
|
+
path?: Set<object>,
|
|
4238
|
+
budget?: { remaining: number },
|
|
4239
|
+
depth = 0,
|
|
4240
|
+
): unknown {
|
|
4172
4241
|
const seen = path ?? new Set<object>();
|
|
4173
|
-
const nodes = budget ?? { remaining:
|
|
4174
|
-
if (nodes.remaining-- <= 0)
|
|
4175
|
-
|
|
4242
|
+
const nodes = options.maxNodes === undefined ? undefined : (budget ?? { remaining: options.maxNodes });
|
|
4243
|
+
if (nodes && nodes.remaining-- <= 0) {
|
|
4244
|
+
return cursorJsonSafeLimit(
|
|
4245
|
+
options,
|
|
4246
|
+
`Cursor JSON-safe conversion exceeded the maximum node count of ${options.maxNodes?.toLocaleString("en-US")}.`,
|
|
4247
|
+
);
|
|
4248
|
+
}
|
|
4249
|
+
if (options.maxDepth !== undefined && depth >= options.maxDepth) {
|
|
4250
|
+
return cursorJsonSafeLimit(
|
|
4251
|
+
options,
|
|
4252
|
+
`Cursor JSON-safe conversion exceeded the maximum depth of ${options.maxDepth.toLocaleString("en-US")}.`,
|
|
4253
|
+
);
|
|
4254
|
+
}
|
|
4176
4255
|
if (typeof value === "bigint") {
|
|
4177
4256
|
return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(-Number.MAX_SAFE_INTEGER)
|
|
4178
4257
|
? Number(value)
|
|
@@ -4190,19 +4269,37 @@ function cursorJsonSafeValue(value: unknown, path?: Set<object>, budget?: { rema
|
|
|
4190
4269
|
if (Array.isArray(value)) {
|
|
4191
4270
|
const array: unknown[] = [];
|
|
4192
4271
|
for (const entry of value) {
|
|
4193
|
-
if (nodes.remaining <= 0)
|
|
4194
|
-
|
|
4272
|
+
if (nodes && nodes.remaining <= 0) {
|
|
4273
|
+
cursorJsonSafeLimit(
|
|
4274
|
+
options,
|
|
4275
|
+
`Cursor JSON-safe conversion exceeded the maximum node count of ${options.maxNodes?.toLocaleString("en-US")}.`,
|
|
4276
|
+
);
|
|
4277
|
+
break;
|
|
4278
|
+
}
|
|
4279
|
+
array.push(cursorJsonSafeValueWithOptions(entry, options, seen, nodes, depth + 1));
|
|
4195
4280
|
}
|
|
4196
4281
|
return array;
|
|
4197
4282
|
}
|
|
4198
4283
|
const record: Record<string, unknown> = {};
|
|
4199
4284
|
for (const [key, entry] of Object.entries(value)) {
|
|
4200
|
-
if (key === "$typeName") continue;
|
|
4201
|
-
if (nodes.remaining <= 0)
|
|
4202
|
-
|
|
4285
|
+
if (options.stripTypeName && key === "$typeName") continue;
|
|
4286
|
+
if (nodes && nodes.remaining <= 0) {
|
|
4287
|
+
cursorJsonSafeLimit(
|
|
4288
|
+
options,
|
|
4289
|
+
`Cursor JSON-safe conversion exceeded the maximum node count of ${options.maxNodes?.toLocaleString("en-US")}.`,
|
|
4290
|
+
);
|
|
4291
|
+
break;
|
|
4292
|
+
}
|
|
4293
|
+
Object.defineProperty(record, key, {
|
|
4294
|
+
value: cursorJsonSafeValueWithOptions(entry, options, seen, nodes, depth + 1),
|
|
4295
|
+
enumerable: true,
|
|
4296
|
+
configurable: true,
|
|
4297
|
+
writable: true,
|
|
4298
|
+
});
|
|
4203
4299
|
}
|
|
4204
4300
|
return record;
|
|
4205
|
-
} catch {
|
|
4301
|
+
} catch (error) {
|
|
4302
|
+
if (error instanceof CursorJsonSafeLimitError) throw error;
|
|
4206
4303
|
return null;
|
|
4207
4304
|
} finally {
|
|
4208
4305
|
seen.delete(value);
|
|
@@ -4214,6 +4311,16 @@ export function cursorJsonSafeValueForTest(value: unknown): unknown {
|
|
|
4214
4311
|
return cursorJsonSafeValue(value);
|
|
4215
4312
|
}
|
|
4216
4313
|
|
|
4314
|
+
/** Serialize a generic Cursor JSON boundary without dropping keys or truncating containers. */
|
|
4315
|
+
function cursorJsonSafeStringify(value: unknown): string {
|
|
4316
|
+
return JSON.stringify(cursorJsonSafeValueWithOptions(value, CURSOR_GENERIC_JSON_SAFE_OPTIONS)) ?? "";
|
|
4317
|
+
}
|
|
4318
|
+
|
|
4319
|
+
/** Exported for direct regression coverage of the Cursor serialization boundary. */
|
|
4320
|
+
export function cursorJsonSafeStringifyForTest(value: unknown): string {
|
|
4321
|
+
return cursorJsonSafeStringify(value);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4217
4324
|
function selectMcpToolCall(toolCall: any): any {
|
|
4218
4325
|
return toolCall?.tool?.case === "mcpToolCall" ? toolCall.tool.value : toolCall?.mcpToolCall;
|
|
4219
4326
|
}
|
|
@@ -4589,7 +4696,10 @@ function buildCursorWireToolIdentities(tools: Tool[] | undefined): CursorWireToo
|
|
|
4589
4696
|
return tools
|
|
4590
4697
|
.filter(tool => !CURSOR_NATIVE_TOOL_NAMES.has(tool.name))
|
|
4591
4698
|
.map(tool => {
|
|
4592
|
-
const jsonSchema =
|
|
4699
|
+
const jsonSchema = cursorJsonSafeValueWithOptions(
|
|
4700
|
+
flattenToolRootCombinators(toolWireSchema(tool)),
|
|
4701
|
+
CURSOR_GENERIC_JSON_SAFE_OPTIONS,
|
|
4702
|
+
);
|
|
4593
4703
|
return {
|
|
4594
4704
|
name: tool.name,
|
|
4595
4705
|
description: tool.description || "",
|
|
@@ -4742,12 +4852,12 @@ export function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | u
|
|
|
4742
4852
|
const systemPrompts = normalizeSystemPrompts(systemPrompt);
|
|
4743
4853
|
const jsons =
|
|
4744
4854
|
systemPrompts.length === 0
|
|
4745
|
-
? [
|
|
4746
|
-
: systemPrompts.map(content =>
|
|
4855
|
+
? [cursorJsonSafeStringify({ role: "system", content: "You are a helpful assistant." })]
|
|
4856
|
+
: systemPrompts.map(content => cursorJsonSafeStringify({ role: "system", content }));
|
|
4747
4857
|
// Composer-harness models need anchor/edit discipline pinned ahead of any
|
|
4748
4858
|
// host/default prompt (see composer-discipline.ts for the observed failure modes).
|
|
4749
4859
|
if (modelId !== undefined && isComposerHarnessModel(modelId)) {
|
|
4750
|
-
jsons.unshift(
|
|
4860
|
+
jsons.unshift(cursorJsonSafeStringify({ role: "system", content: CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT }));
|
|
4751
4861
|
}
|
|
4752
4862
|
return jsons;
|
|
4753
4863
|
}
|
|
@@ -4761,7 +4871,7 @@ function buildRootPromptMessagesJson(
|
|
|
4761
4871
|
const lastUserIdx = findLastUserMessageIndex(messages);
|
|
4762
4872
|
|
|
4763
4873
|
const pushJson = (obj: unknown) => {
|
|
4764
|
-
const bytes = new TextEncoder().encode(
|
|
4874
|
+
const bytes = new TextEncoder().encode(cursorJsonSafeStringify(obj));
|
|
4765
4875
|
entries.push(storeCursorBlob(blobStore, bytes));
|
|
4766
4876
|
};
|
|
4767
4877
|
|
|
@@ -4896,6 +5006,18 @@ export function buildCursorUsageToolsKeyForTest(tools: Tool[]): string {
|
|
|
4896
5006
|
return buildCursorUsageToolsKey(tools);
|
|
4897
5007
|
}
|
|
4898
5008
|
|
|
5009
|
+
/** Exported for regression coverage of the generic tool-schema wire boundary. */
|
|
5010
|
+
export function buildCursorWireToolIdentitiesForTest(
|
|
5011
|
+
tools: Tool[],
|
|
5012
|
+
): Array<{ name: string; description: string; inputSchema: JsonValue }> {
|
|
5013
|
+
return buildCursorWireToolIdentities(tools);
|
|
5014
|
+
}
|
|
5015
|
+
|
|
5016
|
+
/** Exported for regression coverage of lossless conversation identity hashing. */
|
|
5017
|
+
export function hashCursorConversationValueForTest(value: unknown): string {
|
|
5018
|
+
return hashCursorConversationValue(value);
|
|
5019
|
+
}
|
|
5020
|
+
|
|
4899
5021
|
/** Exported for tests: decodes Cursor history blobs built from conversation messages. */
|
|
4900
5022
|
export function buildCursorHistoryForTest(messages: Message[]): {
|
|
4901
5023
|
rootPromptMessagesJson: unknown[];
|
|
@@ -4977,9 +5099,7 @@ function hashCursorConversationMessage(message: { role: string; content: unknown
|
|
|
4977
5099
|
}
|
|
4978
5100
|
|
|
4979
5101
|
function hashCursorConversationValue(value: unknown): string {
|
|
4980
|
-
return createHash("sha256")
|
|
4981
|
-
.update(JSON.stringify(value) ?? "")
|
|
4982
|
-
.digest("hex");
|
|
5102
|
+
return createHash("sha256").update(cursorJsonSafeStringify(value)).digest("hex");
|
|
4983
5103
|
}
|
|
4984
5104
|
|
|
4985
5105
|
function canReuseCursorConversationContext(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";
|
|
2
|
-
import { type AssistantMessage, type Context, type Model, type ServiceTier, type StreamFunction, type StreamOptions, type ToolChoice } from "../types";
|
|
2
|
+
import { type AssistantMessage, type Context, type Model, type RepetitionGuardOptions, type ServiceTier, type StreamFunction, type StreamOptions, type ToolChoice } from "../types";
|
|
3
3
|
import { type ResolvedOpenAICompat } from "./openai-completions-compat";
|
|
4
4
|
/** Test seam: the provider base URL as resolved from trusted env. */
|
|
5
5
|
export declare function resolveOpenAICompletionsBaseUrlForTest(baseUrl: string | undefined, authCredentialType: "api_key" | "oauth" | undefined): string;
|
|
@@ -23,6 +23,14 @@ export interface OpenAICompletionsOptions extends StreamOptions {
|
|
|
23
23
|
/** Force-disable reasoning where supported, or request the lowest effort on generic effort endpoints. */
|
|
24
24
|
disableReasoning?: boolean;
|
|
25
25
|
serviceTier?: ServiceTier;
|
|
26
|
+
/**
|
|
27
|
+
* Runaway-repetition guard thresholds, per stream channel. A number sets the
|
|
28
|
+
* consecutive-repeat threshold; `false` disables the channel's guard.
|
|
29
|
+
* Defaults: thinking = DEFAULT_REPETITION_THRESHOLD, text = false — visible
|
|
30
|
+
* output is a deliverable and intentional repetition there (logs, fixtures,
|
|
31
|
+
* tables, generated code) must survive byte for byte (#5627).
|
|
32
|
+
*/
|
|
33
|
+
repetitionGuard?: RepetitionGuardOptions;
|
|
26
34
|
}
|
|
27
35
|
export declare const streamOpenAICompletions: StreamFunction<"openai-completions">;
|
|
28
36
|
export declare function parseChunkUsage(rawUsage: object, model: Model<"openai-completions">, premiumRequests: number | undefined): AssistantMessage["usage"];
|