@gajae-code/ai 0.17.1 → 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 +117 -0
- package/dist/types/auth-gateway/server.d.ts +23 -1
- package/dist/types/auth-storage.d.ts +12 -1
- package/dist/types/model-thinking.d.ts +10 -6
- package/dist/types/provider-models/openai-compat.d.ts +2 -2
- 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 -2
- 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-manager.ts +11 -8
- package/src/model-pricing.ts +22 -0
- package/src/model-thinking.d.ts +10 -6
- package/src/model-thinking.ts +93 -11
- package/src/models.json +241 -15
- package/src/provider-models/openai-compat.ts +27 -19
- package/src/providers/anthropic.d.ts +1 -1
- package/src/providers/anthropic.ts +10 -2
- package/src/providers/cursor.d.ts +10 -0
- package/src/providers/cursor.ts +176 -31
- package/src/providers/openai-completions.d.ts +9 -1
- package/src/providers/openai-completions.ts +379 -128
- package/src/providers/openai-opencodex-responses.ts +15 -5
- package/src/stream.ts +24 -1
- 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 +70 -7
- package/src/utils/http-inspector.ts +4 -2
- 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/providers/cursor.ts
CHANGED
|
@@ -676,6 +676,12 @@ const CURSOR_WRITE_DRAIN_TIMEOUT_MS = 5_000;
|
|
|
676
676
|
const CURSOR_MAX_PENDING_SHELL_WRITE_BYTES = 1024 * 1024;
|
|
677
677
|
const pendingCursorWrites = new WeakMap<object, Set<Promise<void>>>();
|
|
678
678
|
const cursorWriteErrors = new WeakMap<object, unknown>();
|
|
679
|
+
interface CursorWriteListeners {
|
|
680
|
+
finishes: Set<(error?: unknown) => void>;
|
|
681
|
+
onError: (error: unknown) => void;
|
|
682
|
+
onClose: () => void;
|
|
683
|
+
}
|
|
684
|
+
const cursorWriteListeners = new WeakMap<object, CursorWriteListeners>();
|
|
679
685
|
|
|
680
686
|
function closeStalledCursorRequest(request: http2.ClientHttp2Stream): void {
|
|
681
687
|
// A request whose peer stopped reading may never invoke a write callback. Close
|
|
@@ -833,25 +839,44 @@ function writeCursorFrame(request: http2.ClientHttp2Stream, frame: Uint8Array):
|
|
|
833
839
|
// late transport error cannot surface as an unhandled rejection in the gap.
|
|
834
840
|
completion.promise.catch(() => {});
|
|
835
841
|
pending.add(completion.promise);
|
|
842
|
+
let listeners = cursorWriteListeners.get(request);
|
|
843
|
+
if (!listeners) {
|
|
844
|
+
const finishes = new Set<(error?: unknown) => void>();
|
|
845
|
+
const onError = (error: unknown) => {
|
|
846
|
+
for (const finish of [...finishes]) finish(error);
|
|
847
|
+
};
|
|
848
|
+
listeners = {
|
|
849
|
+
finishes,
|
|
850
|
+
onError,
|
|
851
|
+
onClose: () => onError(new Error("Cursor request closed before write completed")),
|
|
852
|
+
};
|
|
853
|
+
cursorWriteListeners.set(request, listeners);
|
|
854
|
+
}
|
|
855
|
+
const shared = listeners;
|
|
836
856
|
const finish = (error?: unknown) => {
|
|
837
857
|
if (completed) return;
|
|
838
858
|
completed = true;
|
|
839
859
|
pending.delete(completion.promise);
|
|
840
860
|
if (error != null && !cursorWriteErrors.has(request)) cursorWriteErrors.set(request, error);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
request.removeListener
|
|
861
|
+
shared.finishes.delete(finish);
|
|
862
|
+
if (shared.finishes.size === 0) {
|
|
863
|
+
if (typeof request.removeListener === "function") {
|
|
864
|
+
request.removeListener("close", shared.onClose);
|
|
865
|
+
request.removeListener("error", shared.onError);
|
|
866
|
+
}
|
|
867
|
+
cursorWriteListeners.delete(request);
|
|
868
|
+
// Keep the pending set and first error until the final drain observes them.
|
|
844
869
|
}
|
|
845
870
|
if (error == null) completion.resolve();
|
|
846
871
|
else completion.reject(error);
|
|
847
872
|
};
|
|
848
|
-
|
|
873
|
+
shared.finishes.add(finish);
|
|
849
874
|
try {
|
|
850
875
|
// The real HTTP/2 stream always exposes EventEmitter methods. Keep the
|
|
851
876
|
// test seam tolerant of a minimal writer stub as well.
|
|
852
|
-
if (typeof request.once === "function") {
|
|
853
|
-
request.once("close", onClose);
|
|
854
|
-
request.once("error",
|
|
877
|
+
if (shared.finishes.size === 1 && typeof request.once === "function") {
|
|
878
|
+
request.once("close", shared.onClose);
|
|
879
|
+
request.once("error", shared.onError);
|
|
855
880
|
}
|
|
856
881
|
return request.write(frame, finish) !== false;
|
|
857
882
|
} catch (error) {
|
|
@@ -1263,6 +1288,9 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1263
1288
|
let terminalDrainStarted = false;
|
|
1264
1289
|
let execQueuePrefix: Promise<void> | undefined;
|
|
1265
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>();
|
|
1266
1294
|
let terminalBoundarySeen = false;
|
|
1267
1295
|
// Lookahead can validate turnEnded while an exec handler holds the normal
|
|
1268
1296
|
// parser. Close new exec admission immediately, but leave the validated
|
|
@@ -1570,6 +1598,12 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1570
1598
|
};
|
|
1571
1599
|
h2RequestErrorHandler = error => {
|
|
1572
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);
|
|
1573
1607
|
terminalize(error, "drainable");
|
|
1574
1608
|
};
|
|
1575
1609
|
h2Request.on("error", h2RequestErrorHandler);
|
|
@@ -1612,7 +1646,10 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
1612
1646
|
return;
|
|
1613
1647
|
}
|
|
1614
1648
|
responseEnded = true;
|
|
1615
|
-
|
|
1649
|
+
requestCloseError = Object.assign(new Error(`Cursor HTTP/2 request ${kind} before turnEnded`), {
|
|
1650
|
+
http2RstCode: h2Request?.rstCode,
|
|
1651
|
+
});
|
|
1652
|
+
terminalize(requestCloseError, "drainable");
|
|
1616
1653
|
};
|
|
1617
1654
|
h2RequestCloseHandler = () => handleUnexpectedRequestClose("closed");
|
|
1618
1655
|
h2RequestAbortedHandler = () => handleUnexpectedRequestClose("aborted");
|
|
@@ -2308,6 +2345,15 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
|
|
|
2308
2345
|
output.stopReason = callerAbortError || options?.signal?.aborted ? "aborted" : "error";
|
|
2309
2346
|
output.errorStatus = extractHttpStatusFromError(mappedError);
|
|
2310
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
|
+
}
|
|
2311
2357
|
output.errorMessage = formatErrorMessageWithRetryAfter(mappedError);
|
|
2312
2358
|
finalizeCursorUsage(output, usageState);
|
|
2313
2359
|
calculateCost(model, output.usage);
|
|
@@ -4125,6 +4171,42 @@ function cursorNativeToolName(kindKey: string): string {
|
|
|
4125
4171
|
/** Hard node budget for one native-payload conversion; bounds hostile or cyclic graphs. */
|
|
4126
4172
|
const CURSOR_JSON_SAFE_MAX_NODES = 10_000;
|
|
4127
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
|
+
}
|
|
4128
4210
|
|
|
4129
4211
|
/**
|
|
4130
4212
|
* Total conversion of a Cursor protobuf payload into plain JSON-safe data.
|
|
@@ -4137,17 +4219,39 @@ const CURSOR_JSON_SAFE_MAX_DEPTH = 100;
|
|
|
4137
4219
|
* all of which require `JSON.stringify`-safe values. Attaching the raw payload
|
|
4138
4220
|
* is exactly the local-snapshot producer defect class behind issue #4578.
|
|
4139
4221
|
*
|
|
4140
|
-
*
|
|
4141
|
-
* strings beyond `Number.MAX_SAFE_INTEGER`), byte arrays
|
|
4142
|
-
* strings, dates become ISO strings, functions/symbols are
|
|
4143
|
-
* and over-depth values collapse to null, and containers stop
|
|
4144
|
-
* 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.
|
|
4145
4229
|
*/
|
|
4146
|
-
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 {
|
|
4147
4241
|
const seen = path ?? new Set<object>();
|
|
4148
|
-
const nodes = budget ?? { remaining:
|
|
4149
|
-
if (nodes.remaining-- <= 0)
|
|
4150
|
-
|
|
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
|
+
}
|
|
4151
4255
|
if (typeof value === "bigint") {
|
|
4152
4256
|
return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(-Number.MAX_SAFE_INTEGER)
|
|
4153
4257
|
? Number(value)
|
|
@@ -4165,19 +4269,37 @@ function cursorJsonSafeValue(value: unknown, path?: Set<object>, budget?: { rema
|
|
|
4165
4269
|
if (Array.isArray(value)) {
|
|
4166
4270
|
const array: unknown[] = [];
|
|
4167
4271
|
for (const entry of value) {
|
|
4168
|
-
if (nodes.remaining <= 0)
|
|
4169
|
-
|
|
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));
|
|
4170
4280
|
}
|
|
4171
4281
|
return array;
|
|
4172
4282
|
}
|
|
4173
4283
|
const record: Record<string, unknown> = {};
|
|
4174
4284
|
for (const [key, entry] of Object.entries(value)) {
|
|
4175
|
-
if (key === "$typeName") continue;
|
|
4176
|
-
if (nodes.remaining <= 0)
|
|
4177
|
-
|
|
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
|
+
});
|
|
4178
4299
|
}
|
|
4179
4300
|
return record;
|
|
4180
|
-
} catch {
|
|
4301
|
+
} catch (error) {
|
|
4302
|
+
if (error instanceof CursorJsonSafeLimitError) throw error;
|
|
4181
4303
|
return null;
|
|
4182
4304
|
} finally {
|
|
4183
4305
|
seen.delete(value);
|
|
@@ -4189,6 +4311,16 @@ export function cursorJsonSafeValueForTest(value: unknown): unknown {
|
|
|
4189
4311
|
return cursorJsonSafeValue(value);
|
|
4190
4312
|
}
|
|
4191
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
|
+
|
|
4192
4324
|
function selectMcpToolCall(toolCall: any): any {
|
|
4193
4325
|
return toolCall?.tool?.case === "mcpToolCall" ? toolCall.tool.value : toolCall?.mcpToolCall;
|
|
4194
4326
|
}
|
|
@@ -4564,7 +4696,10 @@ function buildCursorWireToolIdentities(tools: Tool[] | undefined): CursorWireToo
|
|
|
4564
4696
|
return tools
|
|
4565
4697
|
.filter(tool => !CURSOR_NATIVE_TOOL_NAMES.has(tool.name))
|
|
4566
4698
|
.map(tool => {
|
|
4567
|
-
const jsonSchema =
|
|
4699
|
+
const jsonSchema = cursorJsonSafeValueWithOptions(
|
|
4700
|
+
flattenToolRootCombinators(toolWireSchema(tool)),
|
|
4701
|
+
CURSOR_GENERIC_JSON_SAFE_OPTIONS,
|
|
4702
|
+
);
|
|
4568
4703
|
return {
|
|
4569
4704
|
name: tool.name,
|
|
4570
4705
|
description: tool.description || "",
|
|
@@ -4717,12 +4852,12 @@ export function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | u
|
|
|
4717
4852
|
const systemPrompts = normalizeSystemPrompts(systemPrompt);
|
|
4718
4853
|
const jsons =
|
|
4719
4854
|
systemPrompts.length === 0
|
|
4720
|
-
? [
|
|
4721
|
-
: systemPrompts.map(content =>
|
|
4855
|
+
? [cursorJsonSafeStringify({ role: "system", content: "You are a helpful assistant." })]
|
|
4856
|
+
: systemPrompts.map(content => cursorJsonSafeStringify({ role: "system", content }));
|
|
4722
4857
|
// Composer-harness models need anchor/edit discipline pinned ahead of any
|
|
4723
4858
|
// host/default prompt (see composer-discipline.ts for the observed failure modes).
|
|
4724
4859
|
if (modelId !== undefined && isComposerHarnessModel(modelId)) {
|
|
4725
|
-
jsons.unshift(
|
|
4860
|
+
jsons.unshift(cursorJsonSafeStringify({ role: "system", content: CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT }));
|
|
4726
4861
|
}
|
|
4727
4862
|
return jsons;
|
|
4728
4863
|
}
|
|
@@ -4736,7 +4871,7 @@ function buildRootPromptMessagesJson(
|
|
|
4736
4871
|
const lastUserIdx = findLastUserMessageIndex(messages);
|
|
4737
4872
|
|
|
4738
4873
|
const pushJson = (obj: unknown) => {
|
|
4739
|
-
const bytes = new TextEncoder().encode(
|
|
4874
|
+
const bytes = new TextEncoder().encode(cursorJsonSafeStringify(obj));
|
|
4740
4875
|
entries.push(storeCursorBlob(blobStore, bytes));
|
|
4741
4876
|
};
|
|
4742
4877
|
|
|
@@ -4871,6 +5006,18 @@ export function buildCursorUsageToolsKeyForTest(tools: Tool[]): string {
|
|
|
4871
5006
|
return buildCursorUsageToolsKey(tools);
|
|
4872
5007
|
}
|
|
4873
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
|
+
|
|
4874
5021
|
/** Exported for tests: decodes Cursor history blobs built from conversation messages. */
|
|
4875
5022
|
export function buildCursorHistoryForTest(messages: Message[]): {
|
|
4876
5023
|
rootPromptMessagesJson: unknown[];
|
|
@@ -4952,9 +5099,7 @@ function hashCursorConversationMessage(message: { role: string; content: unknown
|
|
|
4952
5099
|
}
|
|
4953
5100
|
|
|
4954
5101
|
function hashCursorConversationValue(value: unknown): string {
|
|
4955
|
-
return createHash("sha256")
|
|
4956
|
-
.update(JSON.stringify(value) ?? "")
|
|
4957
|
-
.digest("hex");
|
|
5102
|
+
return createHash("sha256").update(cursorJsonSafeStringify(value)).digest("hex");
|
|
4958
5103
|
}
|
|
4959
5104
|
|
|
4960
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"];
|