@gajae-code/ai 0.18.5 → 0.18.7
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 +73 -0
- package/dist/types/auth-storage.d.ts +8 -1
- package/dist/types/providers/cursor.d.ts +6 -3
- package/dist/types/providers/kiro-api-key.d.ts +1 -0
- package/dist/types/providers/openai-codex-responses.d.ts +3 -0
- package/dist/types/types.d.ts +16 -1
- package/dist/types/utils/fallback-transport.d.ts +6 -0
- package/dist/types/utils/idle-iterator.d.ts +2 -0
- package/dist/types/utils/oauth/glm-zcode.d.ts +5 -0
- package/dist/types/utils/oauth/index.d.ts +4 -0
- package/package.json +3 -3
- package/src/auth-storage.ts +115 -39
- package/src/model-thinking.ts +8 -0
- package/src/providers/aws-eventstream.ts +2 -1
- package/src/providers/cursor.d.ts +6 -3
- package/src/providers/cursor.ts +73 -64
- package/src/providers/google-gemini-cli.ts +49 -3
- package/src/providers/kiro-api-key.d.ts +1 -0
- package/src/providers/kiro-api-key.ts +1 -1
- package/src/providers/kiro-codewhisperer.ts +164 -68
- package/src/providers/openai-codex-responses.d.ts +3 -0
- package/src/providers/openai-codex-responses.ts +589 -41
- package/src/providers/openai-opencodex-responses.ts +6 -1
- package/src/providers/openai-responses.ts +1 -1
- package/src/providers/register-builtins.ts +53 -5
- package/src/stream.ts +20 -0
- package/src/types.d.ts +16 -1
- package/src/types.ts +19 -0
- package/src/utils/fallback-transport.d.ts +6 -0
- package/src/utils/fallback-transport.ts +6 -0
- package/src/utils/idle-iterator.d.ts +2 -0
- package/src/utils/idle-iterator.ts +13 -3
- package/src/utils/oauth/glm-zcode.d.ts +5 -0
- package/src/utils/oauth/glm-zcode.ts +8 -1
- package/src/utils/oauth/index.ts +11 -1
- package/src/utils/validation.ts +5 -0
|
@@ -19,6 +19,8 @@ interface HealthPayload {
|
|
|
19
19
|
ok?: unknown;
|
|
20
20
|
pid?: unknown;
|
|
21
21
|
port?: unknown;
|
|
22
|
+
status?: unknown;
|
|
23
|
+
service?: unknown;
|
|
22
24
|
version?: unknown;
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -103,7 +105,10 @@ async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
|
|
103
105
|
function isOpenCodexHealth(payload: unknown, expectedPort: number): boolean {
|
|
104
106
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
105
107
|
const health = payload as HealthPayload;
|
|
106
|
-
|
|
108
|
+
const identityOk =
|
|
109
|
+
(health.ok === true && health.version === "opencodex") ||
|
|
110
|
+
(health.status === "ok" && health.service === "opencodex");
|
|
111
|
+
return identityOk && health.port === expectedPort;
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
export async function resolveOpenCodexEndpoint(signal?: AbortSignal): Promise<OpenCodexEndpoint | undefined> {
|
|
@@ -1006,7 +1006,7 @@ export function convertTools(tools: Tool[], strictMode: boolean, model: Model<"o
|
|
|
1006
1006
|
name: tool.name,
|
|
1007
1007
|
description: tool.description || "",
|
|
1008
1008
|
parameters,
|
|
1009
|
-
|
|
1009
|
+
strict: effectiveStrict,
|
|
1010
1010
|
} as OpenAITool;
|
|
1011
1011
|
});
|
|
1012
1012
|
// Tool definitions bypass the `input`/`instructions` sanitizers, so a
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* loader: heavy SDKs stay out of the CLI startup parse graph.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { $env } from "@gajae-code/utils";
|
|
14
15
|
import type {
|
|
15
16
|
Api,
|
|
16
17
|
AssistantMessage,
|
|
@@ -280,16 +281,33 @@ function forwardStream<TApi extends Api>(
|
|
|
280
281
|
model.provider,
|
|
281
282
|
limits?.defaultFirstEventTimeoutMs,
|
|
282
283
|
);
|
|
284
|
+
const firstEventTimeoutMs =
|
|
285
|
+
options.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs, firstEventFallbackMs);
|
|
286
|
+
const firstEventTimeoutSource =
|
|
287
|
+
options.streamFirstEventTimeoutMs !== undefined
|
|
288
|
+
? "stream-option"
|
|
289
|
+
: $env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS !== undefined &&
|
|
290
|
+
Number.isFinite(Number($env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS))
|
|
291
|
+
? "env"
|
|
292
|
+
: idleTimeoutMs !== undefined &&
|
|
293
|
+
(firstEventFallbackMs === undefined || idleTimeoutMs >= firstEventFallbackMs)
|
|
294
|
+
? "idle-timeout"
|
|
295
|
+
: firstEventFallbackMs !== undefined
|
|
296
|
+
? "provider-fallback"
|
|
297
|
+
: "default";
|
|
283
298
|
watchedSource = iterateWithIdleTimeout(source, {
|
|
284
299
|
idleTimeoutMs,
|
|
285
|
-
firstItemTimeoutMs:
|
|
286
|
-
options.streamFirstEventTimeoutMs ??
|
|
287
|
-
getStreamFirstEventTimeoutMs(idleTimeoutMs, firstEventFallbackMs),
|
|
300
|
+
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
288
301
|
errorMessage: LAZY_STREAM_IDLE_TIMEOUT_ERROR,
|
|
289
302
|
firstItemErrorMessage: LAZY_STREAM_FIRST_EVENT_TIMEOUT_ERROR,
|
|
290
303
|
onIdle: () => abortTracker.abortLocally(new Error(LAZY_STREAM_IDLE_TIMEOUT_ERROR)),
|
|
291
304
|
onFirstItemTimeout: () =>
|
|
292
|
-
abortTracker.abortLocally(
|
|
305
|
+
abortTracker.abortLocally(
|
|
306
|
+
new FirstEventTimeoutError(LAZY_STREAM_FIRST_EVENT_TIMEOUT_ERROR, {
|
|
307
|
+
firstEventTimeoutMs,
|
|
308
|
+
firstEventTimeoutSource,
|
|
309
|
+
}),
|
|
310
|
+
),
|
|
293
311
|
abortSignal: options.signal,
|
|
294
312
|
// Synthetic starts and tool-capability negotiation are control-plane events,
|
|
295
313
|
// not model progress. Keep the first-event window active until assistant output
|
|
@@ -313,7 +331,7 @@ function forwardStream<TApi extends Api>(
|
|
|
313
331
|
}
|
|
314
332
|
} catch (error) {
|
|
315
333
|
const stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
316
|
-
const message = createLazyLoadErrorMessage(model, error, stopReason);
|
|
334
|
+
const message = createLazyLoadErrorMessage(model, error, stopReason, source);
|
|
317
335
|
target.push({ type: "error", reason: stopReason, error: message });
|
|
318
336
|
target.end(message);
|
|
319
337
|
}
|
|
@@ -324,8 +342,37 @@ function createLazyLoadErrorMessage<TApi extends Api>(
|
|
|
324
342
|
model: Model<TApi>,
|
|
325
343
|
error: unknown,
|
|
326
344
|
stopReason: Extract<AssistantMessage["stopReason"], "aborted" | "error"> = "error",
|
|
345
|
+
source?: AsyncIterable<AssistantMessageEvent>,
|
|
327
346
|
): AssistantMessage {
|
|
328
347
|
const transportFailure = transportFailureFacts(error);
|
|
348
|
+
const carriedDiagnostics =
|
|
349
|
+
source && typeof source === "object"
|
|
350
|
+
? (source as { googleGeminiCliDiagnostics?: AssistantMessage["googleGeminiCliDiagnostics"] })
|
|
351
|
+
.googleGeminiCliDiagnostics
|
|
352
|
+
: undefined;
|
|
353
|
+
const diagnostics =
|
|
354
|
+
carriedDiagnostics ??
|
|
355
|
+
(error instanceof FirstEventTimeoutError
|
|
356
|
+
? (
|
|
357
|
+
error as FirstEventTimeoutError & {
|
|
358
|
+
googleGeminiCliDiagnostics?: AssistantMessage["googleGeminiCliDiagnostics"];
|
|
359
|
+
}
|
|
360
|
+
).googleGeminiCliDiagnostics
|
|
361
|
+
: undefined);
|
|
362
|
+
if (diagnostics && typeof error === "object" && error !== null) {
|
|
363
|
+
const timeoutFacts = error as {
|
|
364
|
+
firstEventTimeoutMs?: unknown;
|
|
365
|
+
firstEventTimeoutSource?: unknown;
|
|
366
|
+
};
|
|
367
|
+
if (typeof timeoutFacts.firstEventTimeoutMs === "number") {
|
|
368
|
+
diagnostics.firstEventTimeoutMs = timeoutFacts.firstEventTimeoutMs;
|
|
369
|
+
}
|
|
370
|
+
if (typeof timeoutFacts.firstEventTimeoutSource === "string") {
|
|
371
|
+
diagnostics.firstEventTimeoutSource = timeoutFacts.firstEventTimeoutSource as NonNullable<
|
|
372
|
+
AssistantMessage["googleGeminiCliDiagnostics"]
|
|
373
|
+
>["firstEventTimeoutSource"];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
329
376
|
return {
|
|
330
377
|
role: "assistant",
|
|
331
378
|
content: [],
|
|
@@ -344,6 +391,7 @@ function createLazyLoadErrorMessage<TApi extends Api>(
|
|
|
344
391
|
errorMessage:
|
|
345
392
|
stopReason === "aborted" ? "Request was aborted" : error instanceof Error ? error.message : String(error),
|
|
346
393
|
...(transportFailure ? { transportFailure } : {}),
|
|
394
|
+
...(diagnostics ? { googleGeminiCliDiagnostics: diagnostics } : {}),
|
|
347
395
|
timestamp: Date.now(),
|
|
348
396
|
};
|
|
349
397
|
}
|
package/src/stream.ts
CHANGED
|
@@ -1022,8 +1022,28 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1022
1022
|
// For older models: use budget-based thinking
|
|
1023
1023
|
if (model.thinking?.mode === "anthropic-adaptive") {
|
|
1024
1024
|
const effort = mapEffortToAnthropicAdaptiveEffort(model, reasoning);
|
|
1025
|
+
|
|
1026
|
+
// Adaptive mode sends effort directly (no explicit budget_tokens).
|
|
1027
|
+
// However, the thinking consumes tokens from the overall max_tokens budget.
|
|
1028
|
+
// If max_tokens is capped at DEFAULT_REQUEST_MAX_TOKENS (32k),
|
|
1029
|
+
// high/xhigh/max reasoning consumes all 32k, leaving no output tokens.
|
|
1030
|
+
// Solution: when caller did not explicitly set maxTokens,
|
|
1031
|
+
// increase the cap to model.maxTokens to allow room for output.
|
|
1032
|
+
let adaptiveMaxTokens = base.maxTokens ?? model.maxTokens;
|
|
1033
|
+
const hasExplicitMaxTokens = Number.isSafeInteger(options?.maxTokens) && (options?.maxTokens as number) > 0;
|
|
1034
|
+
if (
|
|
1035
|
+
!hasExplicitMaxTokens &&
|
|
1036
|
+
adaptiveMaxTokens === DEFAULT_REQUEST_MAX_TOKENS &&
|
|
1037
|
+
Number.isSafeInteger(model.maxTokens) &&
|
|
1038
|
+
model.maxTokens > DEFAULT_REQUEST_MAX_TOKENS
|
|
1039
|
+
) {
|
|
1040
|
+
// Increase to model.maxTokens to leave room for both thinking and output
|
|
1041
|
+
adaptiveMaxTokens = model.maxTokens;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1025
1044
|
return castApi<"anthropic-messages">({
|
|
1026
1045
|
...base,
|
|
1046
|
+
maxTokens: adaptiveMaxTokens,
|
|
1027
1047
|
thinkingEnabled: true,
|
|
1028
1048
|
effort,
|
|
1029
1049
|
toolChoice: mapAnthropicToolChoice(options?.toolChoice),
|
package/src/types.d.ts
CHANGED
|
@@ -640,6 +640,8 @@ export interface AssistantMessage {
|
|
|
640
640
|
* supplied no trustworthy structured evidence.
|
|
641
641
|
*/
|
|
642
642
|
providerDiagnostic?: ProviderDiagnostic;
|
|
643
|
+
/** Redacted first-event transport timing and chunk-shape diagnostics for Google Gemini CLI. */
|
|
644
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
643
645
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
644
646
|
transportFailure?: TransportFailureFacts;
|
|
645
647
|
/**
|
|
@@ -658,6 +660,19 @@ export interface AssistantMessage {
|
|
|
658
660
|
/** Prompt-prefix fingerprint of the request that produced this message. */
|
|
659
661
|
promptPrefix?: PromptPrefixTelemetry;
|
|
660
662
|
}
|
|
663
|
+
export interface GoogleGeminiCliDiagnostics {
|
|
664
|
+
responseAtMs?: number;
|
|
665
|
+
firstRawSseAtMs?: number;
|
|
666
|
+
chunkCounts: {
|
|
667
|
+
content: number;
|
|
668
|
+
thinking: number;
|
|
669
|
+
functionCall: number;
|
|
670
|
+
usageOnly: number;
|
|
671
|
+
other: number;
|
|
672
|
+
};
|
|
673
|
+
firstEventTimeoutMs?: number;
|
|
674
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
675
|
+
}
|
|
661
676
|
export interface ToolResultMessage<TDetails = any> {
|
|
662
677
|
role: "toolResult";
|
|
663
678
|
toolCallId: string;
|
|
@@ -746,7 +761,7 @@ export type TSchema = ZodType | TJsonSchema;
|
|
|
746
761
|
export type Static<S> = S extends ZodType ? z.infer<S> : S extends {
|
|
747
762
|
static: infer T;
|
|
748
763
|
} ? T : unknown;
|
|
749
|
-
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";
|
|
764
|
+
export type RawArgumentRejectionCode = "ask-question-body-required" | "ask-deep-interview-metadata-required" | "ask-deep-interview-single-question-required" | "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";
|
|
750
765
|
/**
|
|
751
766
|
* Optional structured detail attached to a raw-argument rejection. The fixed
|
|
752
767
|
* per-code guidance in `RAW_ARGUMENT_REJECTION_MESSAGES` explains the shape;
|
package/src/types.ts
CHANGED
|
@@ -881,6 +881,8 @@ export interface AssistantMessage {
|
|
|
881
881
|
* supplied no trustworthy structured evidence.
|
|
882
882
|
*/
|
|
883
883
|
providerDiagnostic?: ProviderDiagnostic;
|
|
884
|
+
/** Redacted first-event transport timing and chunk-shape diagnostics for Google Gemini CLI. */
|
|
885
|
+
googleGeminiCliDiagnostics?: GoogleGeminiCliDiagnostics;
|
|
884
886
|
/** Typed upstream failure facts retained for retry classification without parsing errorMessage. */
|
|
885
887
|
transportFailure?: TransportFailureFacts;
|
|
886
888
|
/**
|
|
@@ -900,6 +902,20 @@ export interface AssistantMessage {
|
|
|
900
902
|
promptPrefix?: PromptPrefixTelemetry;
|
|
901
903
|
}
|
|
902
904
|
|
|
905
|
+
export interface GoogleGeminiCliDiagnostics {
|
|
906
|
+
responseAtMs?: number;
|
|
907
|
+
firstRawSseAtMs?: number;
|
|
908
|
+
chunkCounts: {
|
|
909
|
+
content: number;
|
|
910
|
+
thinking: number;
|
|
911
|
+
functionCall: number;
|
|
912
|
+
usageOnly: number;
|
|
913
|
+
other: number;
|
|
914
|
+
};
|
|
915
|
+
firstEventTimeoutMs?: number;
|
|
916
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
917
|
+
}
|
|
918
|
+
|
|
903
919
|
export interface ToolResultMessage<TDetails = any> {
|
|
904
920
|
role: "toolResult";
|
|
905
921
|
toolCallId: string;
|
|
@@ -1019,6 +1035,9 @@ export type TSchema = ZodType | TJsonSchema;
|
|
|
1019
1035
|
export type Static<S> = S extends ZodType ? z.infer<S> : S extends { static: infer T } ? T : unknown;
|
|
1020
1036
|
|
|
1021
1037
|
export type RawArgumentRejectionCode =
|
|
1038
|
+
| "ask-question-body-required"
|
|
1039
|
+
| "ask-deep-interview-metadata-required"
|
|
1040
|
+
| "ask-deep-interview-single-question-required"
|
|
1022
1041
|
| "ask-deep-interview-question-body-required"
|
|
1023
1042
|
| "ask-intent-review-requires-positive-round"
|
|
1024
1043
|
| "ask-intent-contract-requires-non-empty-authority"
|
|
@@ -23,6 +23,12 @@ 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
|
+
* Terminal error code for a successful HTTP response whose body is not the protocol the
|
|
28
|
+
* provider speaks (for example a JSON or HTML error returned with 200). Deterministic for
|
|
29
|
+
* the request, so session retry and managed fallback must surface it instead of replaying.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PROVIDER_PROTOCOL_MISMATCH_ERROR_CODE = "provider_protocol_mismatch";
|
|
26
32
|
/**
|
|
27
33
|
* OpenAI's typed capacity-overload code. It arrives without an HTTP status —
|
|
28
34
|
* inside an HTTP 200 terminal Responses envelope or a Codex error event — so the
|
|
@@ -26,6 +26,12 @@ 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
|
+
* Terminal error code for a successful HTTP response whose body is not the protocol the
|
|
31
|
+
* provider speaks (for example a JSON or HTML error returned with 200). Deterministic for
|
|
32
|
+
* the request, so session retry and managed fallback must surface it instead of replaying.
|
|
33
|
+
*/
|
|
34
|
+
export const PROVIDER_PROTOCOL_MISMATCH_ERROR_CODE = "provider_protocol_mismatch";
|
|
29
35
|
/**
|
|
30
36
|
* OpenAI's typed capacity-overload code. It arrives without an HTTP status —
|
|
31
37
|
* inside an HTTP 200 terminal Responses envelope or a Codex error event — so the
|
|
@@ -72,6 +72,7 @@ export interface FirstEventTimeoutFacts {
|
|
|
72
72
|
requestBytes?: number;
|
|
73
73
|
firstEventElapsedMs?: number;
|
|
74
74
|
firstEventTimeoutMs?: number;
|
|
75
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
75
76
|
endpointClass?: "canonical" | "custom";
|
|
76
77
|
retryMaxAttempts?: number;
|
|
77
78
|
}
|
|
@@ -80,6 +81,7 @@ export declare class FirstEventTimeoutError extends Error {
|
|
|
80
81
|
readonly requestBytes?: number;
|
|
81
82
|
readonly firstEventElapsedMs?: number;
|
|
82
83
|
readonly firstEventTimeoutMs?: number;
|
|
84
|
+
readonly firstEventTimeoutSource?: FirstEventTimeoutFacts["firstEventTimeoutSource"];
|
|
83
85
|
readonly endpointClass?: "canonical" | "custom";
|
|
84
86
|
readonly retryMaxAttempts?: number;
|
|
85
87
|
constructor(message: string, facts?: FirstEventTimeoutFacts);
|
|
@@ -179,6 +179,7 @@ export interface FirstEventTimeoutFacts {
|
|
|
179
179
|
requestBytes?: number;
|
|
180
180
|
firstEventElapsedMs?: number;
|
|
181
181
|
firstEventTimeoutMs?: number;
|
|
182
|
+
firstEventTimeoutSource?: "stream-option" | "env" | "idle-timeout" | "provider-fallback" | "default";
|
|
182
183
|
endpointClass?: "canonical" | "custom";
|
|
183
184
|
retryMaxAttempts?: number;
|
|
184
185
|
}
|
|
@@ -188,6 +189,7 @@ export class FirstEventTimeoutError extends Error {
|
|
|
188
189
|
readonly requestBytes?: number;
|
|
189
190
|
readonly firstEventElapsedMs?: number;
|
|
190
191
|
readonly firstEventTimeoutMs?: number;
|
|
192
|
+
readonly firstEventTimeoutSource?: FirstEventTimeoutFacts["firstEventTimeoutSource"];
|
|
191
193
|
readonly endpointClass?: "canonical" | "custom";
|
|
192
194
|
readonly retryMaxAttempts?: number;
|
|
193
195
|
|
|
@@ -197,11 +199,19 @@ export class FirstEventTimeoutError extends Error {
|
|
|
197
199
|
this.requestBytes = facts.requestBytes;
|
|
198
200
|
this.firstEventElapsedMs = facts.firstEventElapsedMs;
|
|
199
201
|
this.firstEventTimeoutMs = facts.firstEventTimeoutMs;
|
|
202
|
+
this.firstEventTimeoutSource = facts.firstEventTimeoutSource;
|
|
200
203
|
this.endpointClass = facts.endpointClass;
|
|
201
204
|
this.retryMaxAttempts = facts.retryMaxAttempts;
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Longest delay setTimeout honors. Bun, like Node, fires any larger delay after 1 ms,
|
|
210
|
+
* so a very long timeout (for example an env value meant as "effectively never")
|
|
211
|
+
* would otherwise abort the stream immediately.
|
|
212
|
+
*/
|
|
213
|
+
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
214
|
+
|
|
205
215
|
const dummyWatchdog = setTimeout(() => {}, 1);
|
|
206
216
|
clearTimeout(dummyWatchdog);
|
|
207
217
|
|
|
@@ -211,7 +221,7 @@ clearTimeout(dummyWatchdog);
|
|
|
211
221
|
*/
|
|
212
222
|
export function createWatchdog(timeoutMs: number | undefined, onTimeout: () => void): Watchdog {
|
|
213
223
|
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
214
|
-
return setTimeout(onTimeout, timeoutMs);
|
|
224
|
+
return setTimeout(onTimeout, Math.min(timeoutMs, MAX_TIMER_DELAY_MS));
|
|
215
225
|
}
|
|
216
226
|
return undefined;
|
|
217
227
|
}
|
|
@@ -335,10 +345,10 @@ export async function* iterateWithIdleTimeout<T>(
|
|
|
335
345
|
let timer: NodeJS.Timeout | undefined;
|
|
336
346
|
let resolveTimeout: ((value: { kind: "timeout" }) => void) | undefined;
|
|
337
347
|
const enforceTimeout = !noTimeoutEnforced && activeTimeoutMs !== undefined && activeTimeoutMs >= 0;
|
|
338
|
-
if (enforceTimeout) {
|
|
348
|
+
if (enforceTimeout && activeTimeoutMs !== undefined) {
|
|
339
349
|
const { promise, resolve } = Promise.withResolvers<{ kind: "timeout" }>();
|
|
340
350
|
resolveTimeout = resolve;
|
|
341
|
-
timer = setTimeout(() => resolve({ kind: "timeout" }), activeTimeoutMs);
|
|
351
|
+
timer = setTimeout(() => resolve({ kind: "timeout" }), Math.min(activeTimeoutMs, MAX_TIMER_DELAY_MS));
|
|
342
352
|
racers.push(promise);
|
|
343
353
|
}
|
|
344
354
|
|
|
@@ -57,6 +57,11 @@ export declare class GlmZcodeOAuthFlow extends OAuthCallbackFlow {
|
|
|
57
57
|
}>;
|
|
58
58
|
exchangeToken(code: string, state: string, redirectUri: string): Promise<OAuthCredentials>;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Manual-input prompt for the paste fallback. Repeats the redirect-URL shape and
|
|
62
|
+
* the DevTools pointer so a re-prompt after a rejected paste stays self-contained.
|
|
63
|
+
*/
|
|
64
|
+
export declare const GLM_ZCODE_MANUAL_INPUT_PROMPT = "Paste the full zcode://oauth/callback?code=\u2026&state=\u2026 URL (copy it from browser DevTools \u2192 Network tab; the address bar never shows it) or the authorization code:";
|
|
60
65
|
export declare function loginGlmZcode(ctrl: OAuthController, options?: GlmZcodeOAuthFlowOptions): Promise<OAuthCredentials>;
|
|
61
66
|
export interface GlmZcodeRefreshOptions {
|
|
62
67
|
signal?: AbortSignal;
|
|
@@ -387,7 +387,7 @@ export class GlmZcodeOAuthFlow extends OAuthCallbackFlow {
|
|
|
387
387
|
return {
|
|
388
388
|
url: `${authorizeUrl}?${params.toString()}`,
|
|
389
389
|
instructions:
|
|
390
|
-
"Complete Z.AI login in your browser. This is an UNOFFICIAL ZCode-based login — use at your own risk; it may stop working or violate ZCode/Z.AI Terms of Service.
|
|
390
|
+
"Complete Z.AI login in your browser. This is an UNOFFICIAL ZCode-based login — use at your own risk; it may stop working or violate ZCode/Z.AI Terms of Service. If the ZCode desktop app is installed, cancel the browser's prompt to open it: the app exchanges the single-use code itself and the pasted code is then rejected (broker error 2007). After login the browser redirects to a zcode:// address, which never appears in the address bar — copy it from DevTools instead: open the browser DevTools Network tab (filter: zcode), select the zcode://oauth/callback?code=…&state=… request, and paste that full URL (or just the code) at the prompt.",
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
393
|
|
|
@@ -396,6 +396,13 @@ export class GlmZcodeOAuthFlow extends OAuthCallbackFlow {
|
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Manual-input prompt for the paste fallback. Repeats the redirect-URL shape and
|
|
401
|
+
* the DevTools pointer so a re-prompt after a rejected paste stays self-contained.
|
|
402
|
+
*/
|
|
403
|
+
export const GLM_ZCODE_MANUAL_INPUT_PROMPT =
|
|
404
|
+
"Paste the full zcode://oauth/callback?code=…&state=… URL (copy it from browser DevTools → Network tab; the address bar never shows it) or the authorization code:";
|
|
405
|
+
|
|
399
406
|
export async function loginGlmZcode(
|
|
400
407
|
ctrl: OAuthController,
|
|
401
408
|
options?: GlmZcodeOAuthFlowOptions,
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -9,6 +9,16 @@ import type {
|
|
|
9
9
|
OAuthProviderInterface,
|
|
10
10
|
} from "./types";
|
|
11
11
|
|
|
12
|
+
export class UnknownOAuthProviderError extends Error {
|
|
13
|
+
readonly provider: string;
|
|
14
|
+
|
|
15
|
+
constructor(provider: string) {
|
|
16
|
+
super(`Unknown OAuth provider: ${provider}`);
|
|
17
|
+
this.name = "UnknownOAuthProviderError";
|
|
18
|
+
this.provider = provider;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
13
23
|
{
|
|
14
24
|
id: "anthropic",
|
|
@@ -442,7 +452,7 @@ export async function refreshOAuthToken(
|
|
|
442
452
|
newCredentials = credentials;
|
|
443
453
|
break;
|
|
444
454
|
default:
|
|
445
|
-
throw new
|
|
455
|
+
throw new UnknownOAuthProviderError(provider);
|
|
446
456
|
}
|
|
447
457
|
return newCredentials;
|
|
448
458
|
}
|
package/src/utils/validation.ts
CHANGED
|
@@ -960,6 +960,11 @@ export function validateToolCall(tools: Tool[], toolCall: ToolCall): ToolCall["a
|
|
|
960
960
|
}
|
|
961
961
|
|
|
962
962
|
const RAW_ARGUMENT_REJECTION_MESSAGES: Record<RawArgumentRejectionCode, string> = {
|
|
963
|
+
"ask-question-body-required": "ask question bodies must contain non-whitespace text; remove empty extra questions",
|
|
964
|
+
"ask-deep-interview-metadata-required":
|
|
965
|
+
"active deep-interview questions require structured deepInterview metadata; question prose and workflowGate do not record an answer",
|
|
966
|
+
"ask-deep-interview-single-question-required":
|
|
967
|
+
"active deep-interview asks require exactly one question; include metadata on that question, never as an extra question",
|
|
963
968
|
"ask-deep-interview-question-body-required":
|
|
964
969
|
"deep-interview question bodies must contain a specific question, not a placeholder",
|
|
965
970
|
"ask-intent-review-requires-positive-round":
|