@gajae-code/ai 0.15.3 → 0.15.5
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 +14 -1
- package/dist/types/auth-storage.d.ts +23 -9
- package/dist/types/provider-models/special.d.ts +2 -1
- package/dist/types/providers/kiro-api-key.d.ts +50 -0
- package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
- package/dist/types/types.d.ts +14 -23
- package/dist/types/utils/fallback-transport.d.ts +7 -0
- package/dist/types/utils/json-parse.d.ts +5 -3
- package/dist/types/utils/tool-call-healing.d.ts +4 -4
- package/package.json +3 -3
- package/src/auth-storage.ts +93 -12
- package/src/model-thinking.ts +16 -0
- package/src/models.json +31 -0
- package/src/models.ts +9 -1
- package/src/provider-models/special.ts +15 -3
- package/src/providers/kiro-api-key.d.ts +50 -0
- package/src/providers/kiro-api-key.ts +786 -0
- package/src/providers/kiro-codewhisperer.d.ts +3 -0
- package/src/providers/kiro-codewhisperer.ts +11 -2
- package/src/providers/openai-codex-responses.ts +4 -0
- package/src/providers/openai-responses-shared.ts +23 -2
- package/src/stream.ts +5 -3
- package/src/types.d.ts +14 -23
- package/src/types.ts +14 -22
- package/src/utils/fallback-transport.d.ts +7 -0
- package/src/utils/fallback-transport.ts +21 -4
- package/src/utils/idle-iterator.ts +6 -0
- package/src/utils/json-parse.d.ts +5 -3
- package/src/utils/json-parse.ts +6 -6
- package/src/utils/tool-call-healing.d.ts +4 -4
- package/src/utils/tool-call-healing.ts +4 -4
- package/src/utils/validation.ts +2 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { Effort } from "../model-thinking";
|
|
1
2
|
import type { StreamFunction, StreamOptions } from "../types";
|
|
2
3
|
export interface KiroCodeWhispererOptions extends StreamOptions {
|
|
4
|
+
/** Effort level for Kiro API-key reasoning. */
|
|
5
|
+
reasoning?: Effort | boolean;
|
|
3
6
|
/** AWS region for the CodeWhisperer streaming endpoint. */
|
|
4
7
|
region?: string;
|
|
5
8
|
/** Profile ARN for enterprise IAM Identity Center accounts. */
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* not from any AGPL reference implementation.
|
|
12
12
|
*/
|
|
13
13
|
import { $credentialEnv, $env, extractHttpStatusFromError } from "@gajae-code/utils";
|
|
14
|
+
import type { Effort } from "../model-thinking";
|
|
14
15
|
import type {
|
|
15
16
|
Api,
|
|
16
17
|
AssistantMessage,
|
|
@@ -28,12 +29,15 @@ import { transportFailureFacts } from "../utils/fallback-transport";
|
|
|
28
29
|
import { withHttpStatus } from "../utils/http-inspector";
|
|
29
30
|
import { captureUnicodeEscapeEvidence } from "../utils/json-parse";
|
|
30
31
|
import { decodeEventStream } from "./aws-eventstream";
|
|
32
|
+
import { isKiroApiKey, streamKiroApiKey } from "./kiro-api-key";
|
|
31
33
|
|
|
32
34
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
35
|
// Provider options
|
|
34
36
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
37
|
|
|
36
38
|
export interface KiroCodeWhispererOptions extends StreamOptions {
|
|
39
|
+
/** Effort level for Kiro API-key reasoning. */
|
|
40
|
+
reasoning?: Effort | boolean;
|
|
37
41
|
/** AWS region for the CodeWhisperer streaming endpoint. */
|
|
38
42
|
region?: string;
|
|
39
43
|
/** Profile ARN for enterprise IAM Identity Center accounts. */
|
|
@@ -143,6 +147,11 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
143
147
|
context: Context,
|
|
144
148
|
options: KiroCodeWhispererOptions,
|
|
145
149
|
): AssistantMessageEventStream => {
|
|
150
|
+
const token = resolveBearerToken(options.apiKey);
|
|
151
|
+
if (isKiroApiKey(token)) {
|
|
152
|
+
return streamKiroApiKey(model, context, { ...options, apiKey: token });
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
const stream = new AssistantMessageEventStream();
|
|
147
156
|
|
|
148
157
|
(async () => {
|
|
@@ -175,7 +184,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
175
184
|
const bearerToken = resolveBearerToken(options.apiKey);
|
|
176
185
|
if (!bearerToken) {
|
|
177
186
|
throw new Error(
|
|
178
|
-
"No Kiro credentials found.
|
|
187
|
+
"No Kiro credentials found. Set KIRO_API_KEY (ksk_ from https://app.kiro.dev/settings/api-keys) or run 'gjc auth-broker login kiro'.",
|
|
179
188
|
);
|
|
180
189
|
}
|
|
181
190
|
|
|
@@ -541,7 +550,7 @@ function handleToolUseEvent(
|
|
|
541
550
|
|
|
542
551
|
function resolveBearerToken(apiKey: string | undefined): string | undefined {
|
|
543
552
|
if (!apiKey) {
|
|
544
|
-
return $credentialEnv("AWS_BEARER_TOKEN_KIRO") ?? undefined;
|
|
553
|
+
return $credentialEnv("KIRO_API_KEY") ?? $credentialEnv("AWS_BEARER_TOKEN_KIRO") ?? undefined;
|
|
545
554
|
}
|
|
546
555
|
|
|
547
556
|
// Structured API key (from getOAuthApiKey) contains the access token as JSON
|
|
@@ -1571,6 +1571,8 @@ function handleOutputItemDone(
|
|
|
1571
1571
|
arguments: terminalArguments as Record<string, unknown>,
|
|
1572
1572
|
};
|
|
1573
1573
|
captureUnicodeEscapeEvidence(toolCall, item.arguments);
|
|
1574
|
+
delete runtime.currentBlock.escapedNonAsciiArguments;
|
|
1575
|
+
delete runtime.currentBlock.escapedUnicodeArgumentEvidence;
|
|
1574
1576
|
Object.assign(runtime.currentBlock, toolCall);
|
|
1575
1577
|
captureUnicodeEscapeEvidence(runtime.currentBlock, item.arguments);
|
|
1576
1578
|
delete (runtime.currentBlock as { partialJson?: string }).partialJson;
|
|
@@ -1625,6 +1627,8 @@ function handleOutputItemDone(
|
|
|
1625
1627
|
arguments: { input: terminalInput },
|
|
1626
1628
|
customWireName: item.name,
|
|
1627
1629
|
};
|
|
1630
|
+
delete runtime.currentBlock.escapedNonAsciiArguments;
|
|
1631
|
+
delete runtime.currentBlock.escapedUnicodeArgumentEvidence;
|
|
1628
1632
|
Object.assign(runtime.currentBlock, toolCall);
|
|
1629
1633
|
delete (runtime.currentBlock as { partialJson?: string }).partialJson;
|
|
1630
1634
|
delete (runtime.currentBlock as { doneInput?: string }).doneInput;
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../types";
|
|
32
32
|
import { normalizeResponsesToolCallId, sanitizeJsonStrings } from "../utils";
|
|
33
33
|
import type { AssistantMessageEventStream } from "../utils/event-stream";
|
|
34
|
+
import { SERVER_OVERLOADED_PROVIDER_CODE } from "../utils/fallback-transport";
|
|
34
35
|
import { captureUnicodeEscapeEvidence, isCompleteJson, parseStreamingJson } from "../utils/json-parse";
|
|
35
36
|
import { areJsonValuesEqual } from "../utils/schema";
|
|
36
37
|
import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
|
|
@@ -981,7 +982,10 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
981
982
|
: typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
|
|
982
983
|
? `status_details: ${statusDetailsReason}`
|
|
983
984
|
: "Unknown error (no error details in response)";
|
|
984
|
-
|
|
985
|
+
// A `cancelled` response is not a capacity rejection and may already
|
|
986
|
+
// have produced observable work, so only the `failed` status carries
|
|
987
|
+
// the typed code onward.
|
|
988
|
+
throw createResponsesFailedError(message, response.status === "failed" ? error?.code : undefined);
|
|
985
989
|
}
|
|
986
990
|
// A response cut short for length (`incomplete`) may have stopped
|
|
987
991
|
// mid-tool-call. Any tool-call item still tracked in `items` never
|
|
@@ -1003,11 +1007,28 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
1003
1007
|
: details?.reason
|
|
1004
1008
|
? `incomplete: ${details.reason}`
|
|
1005
1009
|
: "Unknown error (no error details in response)";
|
|
1006
|
-
throw
|
|
1010
|
+
throw createResponsesFailedError(message, error?.code);
|
|
1007
1011
|
}
|
|
1008
1012
|
}
|
|
1009
1013
|
}
|
|
1010
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* A terminal failure envelope arrives inside an HTTP 200 stream — as
|
|
1017
|
+
* `response.failed`, or as `response.completed` with a `failed` response status
|
|
1018
|
+
* — so the typed `error.code` is the only structured evidence the transport can
|
|
1019
|
+
* keep. Both shapes carry the same structured failure and are typed identically.
|
|
1020
|
+
* Exactly OpenAI's capacity-overload code is carried through as transport facts,
|
|
1021
|
+
* matched case-sensitively; every other failure stays a plain error, so an
|
|
1022
|
+
* untyped, cased, or malformed code can never reach a typed retry admission. The
|
|
1023
|
+
* display message is unchanged either way.
|
|
1024
|
+
*/
|
|
1025
|
+
function createResponsesFailedError(message: string, code: string | undefined): Error {
|
|
1026
|
+
if (code !== SERVER_OVERLOADED_PROVIDER_CODE) return new Error(message);
|
|
1027
|
+
const error = new Error(message) as Error & { openaiErrorCode?: string };
|
|
1028
|
+
error.openaiErrorCode = SERVER_OVERLOADED_PROVIDER_CODE;
|
|
1029
|
+
return error;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1011
1032
|
/**
|
|
1012
1033
|
* Whether two raw JSON argument strings decode to the same value. Used to tell a
|
|
1013
1034
|
* relay's re-serialization of the same tool arguments apart from a genuine
|
package/src/stream.ts
CHANGED
|
@@ -131,10 +131,12 @@ const serviceProviderMap: Record<string, KeyResolver> = {
|
|
|
131
131
|
tavily: "TAVILY_API_KEY",
|
|
132
132
|
parallel: "PARALLEL_API_KEY",
|
|
133
133
|
kagi: "KAGI_API_KEY",
|
|
134
|
-
// Kiro
|
|
134
|
+
// Kiro API keys use the ksk_ prefix; preserve the AWS bearer fallback for OAuth.
|
|
135
135
|
kiro: () => {
|
|
136
|
-
const
|
|
137
|
-
|
|
136
|
+
const apiKey = $credentialEnv("KIRO_API_KEY");
|
|
137
|
+
return apiKey?.trim().startsWith("ksk_") && !/[\x00-\x1f\x7f]/.test(apiKey)
|
|
138
|
+
? apiKey
|
|
139
|
+
: $credentialEnv("AWS_BEARER_TOKEN_KIRO");
|
|
138
140
|
},
|
|
139
141
|
// GitHub Copilot uses GitHub personal access token
|
|
140
142
|
"github-copilot": () => $pickCredentialEnv("COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"),
|
package/src/types.d.ts
CHANGED
|
@@ -399,31 +399,22 @@ export interface ToolCall {
|
|
|
399
399
|
*/
|
|
400
400
|
incompleteArgumentsReason?: "truncated" | "malformed" | "conflicting" | "ambiguous";
|
|
401
401
|
/**
|
|
402
|
-
* Set when
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* The agent loop
|
|
409
|
-
*
|
|
410
|
-
* is a tool that enumerated its display-only fields
|
|
411
|
-
* (`displaySafeEscapedArgFields`): when every escaped scalar corroborates
|
|
412
|
-
* a decoded non-ASCII character inside those fields, the call executes
|
|
413
|
-
* with a warning instead — rendered question text, never executable
|
|
414
|
-
* content, ids, or durable metadata.
|
|
415
|
-
* Escapes that are required (control characters) or unavoidable (lone
|
|
416
|
-
* surrogates) never set this.
|
|
402
|
+
* Set by current producers when raw argument JSON carries unsafe Unicode
|
|
403
|
+
* data, such as malformed escape evidence or a decoded unpaired surrogate.
|
|
404
|
+
* Valid JSON `\uXXXX` escapes are canonical spellings of the decoded string
|
|
405
|
+
* and current producers do not set this flag for them.
|
|
406
|
+
*
|
|
407
|
+
* Legacy producers may still set the flag for any escaped non-ASCII spelling.
|
|
408
|
+
* The agent loop keeps its bounded legacy resample/display-safe behavior for
|
|
409
|
+
* those calls while consuming the transient evidence below.
|
|
417
410
|
*/
|
|
418
411
|
escapedNonAsciiArguments?: boolean;
|
|
419
412
|
/**
|
|
420
|
-
* Bounded, payload-free evidence for
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* `escapedNonAsciiArguments`. The agent consumes and removes this transient
|
|
426
|
-
* field before the tool-call message can become durable.
|
|
413
|
+
* Bounded, payload-free evidence for raw Unicode argument data. Current
|
|
414
|
+
* producers attach it only for unsafe data; legacy producers may attach
|
|
415
|
+
* non-malformed positional evidence used by the display-safe compatibility
|
|
416
|
+
* path. The agent consumes and removes this transient field before the
|
|
417
|
+
* tool-call message can become durable.
|
|
427
418
|
*/
|
|
428
419
|
escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
|
429
420
|
}
|
|
@@ -630,7 +621,7 @@ export type TSchema = ZodType | TJsonSchema;
|
|
|
630
621
|
export type Static<S> = S extends ZodType ? z.infer<S> : S extends {
|
|
631
622
|
static: infer T;
|
|
632
623
|
} ? T : unknown;
|
|
633
|
-
export type RawArgumentRejectionCode = "ask-intent-review-requires-positive-round" | "ask-intent-contract-requires-non-empty-authority" | "ask-deep-interview-metadata-requires-deep-interview-gate" | "ask-round-zero-metadata-requires-full-topology-fields" | "todo-write-unknown-root-key" | "todo-write-unknown-op-entry-key" | "todo-write-unknown-op-value" | "todo-write-done-drop-requires-target" | "todo-write-unknown-init-entry-key";
|
|
624
|
+
export type RawArgumentRejectionCode = "ask-deep-interview-question-body-required" | "ask-intent-review-requires-positive-round" | "ask-intent-contract-requires-non-empty-authority" | "ask-deep-interview-metadata-requires-deep-interview-gate" | "ask-round-zero-metadata-requires-full-topology-fields" | "todo-write-unknown-root-key" | "todo-write-unknown-op-entry-key" | "todo-write-unknown-op-value" | "todo-write-done-drop-requires-target" | "todo-write-unknown-init-entry-key";
|
|
634
625
|
/**
|
|
635
626
|
* Optional structured detail attached to a raw-argument rejection. The fixed
|
|
636
627
|
* per-code guidance in `RAW_ARGUMENT_REJECTION_MESSAGES` explains the shape;
|
package/src/types.ts
CHANGED
|
@@ -620,31 +620,22 @@ export interface ToolCall {
|
|
|
620
620
|
*/
|
|
621
621
|
incompleteArgumentsReason?: "truncated" | "malformed" | "conflicting" | "ambiguous";
|
|
622
622
|
/**
|
|
623
|
-
* Set when
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
* The agent loop
|
|
630
|
-
*
|
|
631
|
-
* is a tool that enumerated its display-only fields
|
|
632
|
-
* (`displaySafeEscapedArgFields`): when every escaped scalar corroborates
|
|
633
|
-
* a decoded non-ASCII character inside those fields, the call executes
|
|
634
|
-
* with a warning instead — rendered question text, never executable
|
|
635
|
-
* content, ids, or durable metadata.
|
|
636
|
-
* Escapes that are required (control characters) or unavoidable (lone
|
|
637
|
-
* surrogates) never set this.
|
|
623
|
+
* Set by current producers when raw argument JSON carries unsafe Unicode
|
|
624
|
+
* data, such as malformed escape evidence or a decoded unpaired surrogate.
|
|
625
|
+
* Valid JSON `\uXXXX` escapes are canonical spellings of the decoded string
|
|
626
|
+
* and current producers do not set this flag for them.
|
|
627
|
+
*
|
|
628
|
+
* Legacy producers may still set the flag for any escaped non-ASCII spelling.
|
|
629
|
+
* The agent loop keeps its bounded legacy resample/display-safe behavior for
|
|
630
|
+
* those calls while consuming the transient evidence below.
|
|
638
631
|
*/
|
|
639
632
|
escapedNonAsciiArguments?: boolean;
|
|
640
633
|
/**
|
|
641
|
-
* Bounded, payload-free evidence for
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
* `escapedNonAsciiArguments`. The agent consumes and removes this transient
|
|
647
|
-
* field before the tool-call message can become durable.
|
|
634
|
+
* Bounded, payload-free evidence for raw Unicode argument data. Current
|
|
635
|
+
* producers attach it only for unsafe data; legacy producers may attach
|
|
636
|
+
* non-malformed positional evidence used by the display-safe compatibility
|
|
637
|
+
* path. The agent consumes and removes this transient field before the
|
|
638
|
+
* tool-call message can become durable.
|
|
648
639
|
*/
|
|
649
640
|
escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
|
650
641
|
}
|
|
@@ -871,6 +862,7 @@ export type TSchema = ZodType | TJsonSchema;
|
|
|
871
862
|
export type Static<S> = S extends ZodType ? z.infer<S> : S extends { static: infer T } ? T : unknown;
|
|
872
863
|
|
|
873
864
|
export type RawArgumentRejectionCode =
|
|
865
|
+
| "ask-deep-interview-question-body-required"
|
|
874
866
|
| "ask-intent-review-requires-positive-round"
|
|
875
867
|
| "ask-intent-contract-requires-non-empty-authority"
|
|
876
868
|
| "ask-deep-interview-metadata-requires-deep-interview-gate"
|
|
@@ -23,6 +23,13 @@ export interface FallbackTrigger {
|
|
|
23
23
|
export declare const STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE = "stream_first_event_timeout";
|
|
24
24
|
/** Stable code for a nominally successful response with no content or token usage. */
|
|
25
25
|
export declare const EMPTY_RESPONSE_PROVIDER_CODE = "empty_response";
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI's typed capacity-overload code. It arrives without an HTTP status —
|
|
28
|
+
* inside an HTTP 200 terminal Responses envelope or a Codex error event — so the
|
|
29
|
+
* code itself is the only structured evidence of the failure and must survive
|
|
30
|
+
* the existence gate below. It is always compared case-sensitively.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SERVER_OVERLOADED_PROVIDER_CODE = "server_is_overloaded";
|
|
26
33
|
export type TransportHeaders = Headers | Record<string, string | undefined>;
|
|
27
34
|
/**
|
|
28
35
|
* Structured facts from an upstream HTTP or transport failure. Retry decisions
|
|
@@ -26,6 +26,13 @@ export interface FallbackTrigger {
|
|
|
26
26
|
export const STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE = "stream_first_event_timeout";
|
|
27
27
|
/** Stable code for a nominally successful response with no content or token usage. */
|
|
28
28
|
export const EMPTY_RESPONSE_PROVIDER_CODE = "empty_response";
|
|
29
|
+
/**
|
|
30
|
+
* OpenAI's typed capacity-overload code. It arrives without an HTTP status —
|
|
31
|
+
* inside an HTTP 200 terminal Responses envelope or a Codex error event — so the
|
|
32
|
+
* code itself is the only structured evidence of the failure and must survive
|
|
33
|
+
* the existence gate below. It is always compared case-sensitively.
|
|
34
|
+
*/
|
|
35
|
+
export const SERVER_OVERLOADED_PROVIDER_CODE = "server_is_overloaded";
|
|
29
36
|
|
|
30
37
|
export type TransportHeaders = Headers | Record<string, string | undefined>;
|
|
31
38
|
|
|
@@ -241,6 +248,11 @@ export function transportFailureFacts(
|
|
|
241
248
|
!isContextOverflowCode(normalizedCode) &&
|
|
242
249
|
normalizedCode !== STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE &&
|
|
243
250
|
normalizedCode !== EMPTY_RESPONSE_PROVIDER_CODE &&
|
|
251
|
+
// Matched case-sensitively against the raw code: this is the one gate a
|
|
252
|
+
// provider-owned code can open on its own, so a cased or padded variant
|
|
253
|
+
// must not materialize facts that would disqualify an unrelated
|
|
254
|
+
// bare-default retry.
|
|
255
|
+
providerCode !== SERVER_OVERLOADED_PROVIDER_CODE &&
|
|
244
256
|
requestBytes === undefined &&
|
|
245
257
|
firstEventElapsedMs === undefined &&
|
|
246
258
|
firstEventTimeoutMs === undefined &&
|
|
@@ -360,12 +372,17 @@ export function classifyFallbackTrigger(
|
|
|
360
372
|
const retryAfterMs =
|
|
361
373
|
parseRetryAfterMilliseconds(headers?.get("retry-after-ms") ?? null) ??
|
|
362
374
|
parseRetryAfterSeconds(headers?.get("retry-after") ?? null);
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
);
|
|
375
|
+
const rawCodes = [facts.openaiErrorCode, facts.anthropicErrorType, facts.providerCode];
|
|
376
|
+
const codes = rawCodes.map(value => value?.toLowerCase());
|
|
366
377
|
const code = codes[0] ?? codes[1] ?? codes[2];
|
|
378
|
+
// The capacity-overload code is provider-owned and is the only code that can
|
|
379
|
+
// classify without a status, so it is matched case-sensitively — the same
|
|
380
|
+
// exactness the parser and the session admission use.
|
|
381
|
+
const rawCode = rawCodes[0] ?? rawCodes[1] ?? rawCodes[2];
|
|
367
382
|
const triggerClass: FallbackTriggerClass =
|
|
368
|
-
code === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE ||
|
|
383
|
+
code === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE ||
|
|
384
|
+
code === EMPTY_RESPONSE_PROVIDER_CODE ||
|
|
385
|
+
(facts.status === undefined && rawCode === SERVER_OVERLOADED_PROVIDER_CODE)
|
|
369
386
|
? "server"
|
|
370
387
|
: isQuotaCode(code)
|
|
371
388
|
? "quota"
|
|
@@ -5,6 +5,11 @@ const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120_000;
|
|
|
5
5
|
const DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS = 100_000;
|
|
6
6
|
const ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MS = 600_000;
|
|
7
7
|
const KIMI_CODE_FIRST_EVENT_TIMEOUT_MS = 300_000;
|
|
8
|
+
// Local LM Studio models can spend several minutes loading weights and filling
|
|
9
|
+
// the prompt before emitting their first SSE event. Keep the shared default
|
|
10
|
+
// for other OpenAI-compatible providers, but avoid aborting legitimate local
|
|
11
|
+
// inference during that startup window.
|
|
12
|
+
const LM_STUDIO_FIRST_EVENT_TIMEOUT_MS = 300_000;
|
|
8
13
|
// Ollama Cloud is a hosted proxy where queueing/cold-start plus long prefill
|
|
9
14
|
// (thinking-mode requests over 1M-context sessions) routinely exceeds 120s before
|
|
10
15
|
// the first streamed token; 300s matches kimi-code's floor for long-reasoning silence.
|
|
@@ -34,6 +39,7 @@ export function isGrokModelId(modelId: string | undefined): boolean {
|
|
|
34
39
|
|
|
35
40
|
export function getProviderFirstEventTimeoutFallbackMs(provider: string): number | undefined {
|
|
36
41
|
if (provider === "alibaba-token-plan") return ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MS;
|
|
42
|
+
if (provider === "lm-studio") return LM_STUDIO_FIRST_EVENT_TIMEOUT_MS;
|
|
37
43
|
if (provider === "ollama-cloud") return OLLAMA_CLOUD_FIRST_EVENT_TIMEOUT_MS;
|
|
38
44
|
return provider === "kimi-code" ? KIMI_CODE_FIRST_EVENT_TIMEOUT_MS : undefined;
|
|
39
45
|
}
|
|
@@ -67,9 +67,11 @@ export declare function collectUnicodeEscapeEvidence(json: string): UnicodeEscap
|
|
|
67
67
|
/**
|
|
68
68
|
* Return evidence only when decoded tool arguments are unsafe to execute.
|
|
69
69
|
*
|
|
70
|
-
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value,
|
|
71
|
+
* including a valid scalar whose hex digits differ from what a caller intended:
|
|
72
|
+
* runtime syntax validation cannot infer author intent after decoding.
|
|
73
|
+
* Malformed escape-bearing JSON, duplicate/deep suspicious escape evidence, and
|
|
74
|
+
* unpaired UTF-16 surrogates keep the fail-closed path.
|
|
73
75
|
*/
|
|
74
76
|
export declare function collectUnsafeUnicodeEscapeEvidence(json: string): UnicodeEscapeEvidence | undefined;
|
|
75
77
|
/** Attach unsafe raw evidence while preserving the existing call-level guard flag. */
|
package/src/utils/json-parse.ts
CHANGED
|
@@ -486,15 +486,13 @@ export function collectUnicodeEscapeEvidence(json: string): UnicodeEscapeEvidenc
|
|
|
486
486
|
|
|
487
487
|
function hasUnpairedUnicodeSurrogate(value: unknown): boolean {
|
|
488
488
|
const pending: unknown[] = [value];
|
|
489
|
-
const seen = new WeakSet<object>();
|
|
490
489
|
while (pending.length > 0) {
|
|
491
490
|
const current = pending.pop();
|
|
492
491
|
if (typeof current === "string") {
|
|
493
492
|
if (!current.isWellFormed()) return true;
|
|
494
493
|
continue;
|
|
495
494
|
}
|
|
496
|
-
if (typeof current !== "object" || current === null
|
|
497
|
-
seen.add(current);
|
|
495
|
+
if (typeof current !== "object" || current === null) continue;
|
|
498
496
|
if (Array.isArray(current)) {
|
|
499
497
|
for (const child of current) pending.push(child);
|
|
500
498
|
continue;
|
|
@@ -510,9 +508,11 @@ function hasUnpairedUnicodeSurrogate(value: unknown): boolean {
|
|
|
510
508
|
/**
|
|
511
509
|
* Return evidence only when decoded tool arguments are unsafe to execute.
|
|
512
510
|
*
|
|
513
|
-
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value
|
|
514
|
-
*
|
|
515
|
-
*
|
|
511
|
+
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value,
|
|
512
|
+
* including a valid scalar whose hex digits differ from what a caller intended:
|
|
513
|
+
* runtime syntax validation cannot infer author intent after decoding.
|
|
514
|
+
* Malformed escape-bearing JSON, duplicate/deep suspicious escape evidence, and
|
|
515
|
+
* unpaired UTF-16 surrogates keep the fail-closed path.
|
|
516
516
|
*/
|
|
517
517
|
export function collectUnsafeUnicodeEscapeEvidence(json: string): UnicodeEscapeEvidence | undefined {
|
|
518
518
|
const hasUnicodeEscape = json.includes("\\u");
|
|
@@ -22,10 +22,10 @@ export interface HealedToolCall {
|
|
|
22
22
|
readonly name: string;
|
|
23
23
|
readonly arguments: string;
|
|
24
24
|
/**
|
|
25
|
-
* Whether the raw leaked payload
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Whether the raw leaked payload carried unsafe Unicode argument data.
|
|
26
|
+
* Captured before the normalizing round-trip below, which would otherwise
|
|
27
|
+
* erase malformed escape evidence. Valid `\uXXXX` spellings decode
|
|
28
|
+
* canonically and leave this false.
|
|
29
29
|
*/
|
|
30
30
|
readonly escapedNonAsciiArguments: boolean;
|
|
31
31
|
readonly escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
|
@@ -40,10 +40,10 @@ export interface HealedToolCall {
|
|
|
40
40
|
readonly name: string;
|
|
41
41
|
readonly arguments: string;
|
|
42
42
|
/**
|
|
43
|
-
* Whether the raw leaked payload
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* Whether the raw leaked payload carried unsafe Unicode argument data.
|
|
44
|
+
* Captured before the normalizing round-trip below, which would otherwise
|
|
45
|
+
* erase malformed escape evidence. Valid `\uXXXX` spellings decode
|
|
46
|
+
* canonically and leave this false.
|
|
47
47
|
*/
|
|
48
48
|
readonly escapedNonAsciiArguments: boolean;
|
|
49
49
|
readonly escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
package/src/utils/validation.ts
CHANGED
|
@@ -959,6 +959,8 @@ export function validateToolCall(tools: Tool[], toolCall: ToolCall): ToolCall["a
|
|
|
959
959
|
}
|
|
960
960
|
|
|
961
961
|
const RAW_ARGUMENT_REJECTION_MESSAGES: Record<RawArgumentRejectionCode, string> = {
|
|
962
|
+
"ask-deep-interview-question-body-required":
|
|
963
|
+
"deep-interview question bodies must contain a specific question, not a placeholder",
|
|
962
964
|
"ask-intent-review-requires-positive-round":
|
|
963
965
|
"deepInterview.intent_review is post-Round-0 only and requires a positive round",
|
|
964
966
|
"ask-intent-contract-requires-non-empty-authority":
|