@gajae-code/ai 0.16.7 → 0.17.1
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 +44 -1
- package/dist/types/auth-storage.d.ts +23 -4
- package/dist/types/models.d.ts +14 -0
- package/dist/types/provider-models/special.d.ts +12 -0
- package/dist/types/providers/anthropic.d.ts +1 -1
- package/dist/types/providers/cursor.d.ts +33 -21
- package/dist/types/providers/devin-acp.d.ts +157 -0
- package/dist/types/providers/google-gemini-headers.d.ts +1 -1
- package/dist/types/providers/mock.d.ts +2 -0
- package/dist/types/providers/openai-responses-shared.d.ts +21 -1
- package/dist/types/providers/register-builtins.d.ts +1 -0
- package/dist/types/types.d.ts +52 -11
- package/dist/types/utils/block-symbols.d.ts +15 -5
- package/dist/types/utils/fallback-transport.d.ts +4 -1
- package/dist/types/utils.d.ts +13 -0
- package/package.json +4 -3
- package/src/api-registry.ts +1 -0
- package/src/auth-broker/redact.ts +10 -2
- package/src/auth-gateway/server.ts +56 -3
- package/src/auth-storage.ts +330 -116
- package/src/model-manager.ts +21 -2
- package/src/models.d.ts +14 -0
- package/src/models.json +117 -0
- package/src/models.ts +18 -0
- package/src/provider-models/descriptors.ts +7 -0
- package/src/provider-models/openai-compat.ts +14 -0
- package/src/provider-models/special.ts +39 -0
- package/src/providers/anthropic.d.ts +1 -1
- package/src/providers/anthropic.ts +1 -1
- package/src/providers/azure-openai-responses.ts +10 -1
- package/src/providers/cursor.d.ts +33 -21
- package/src/providers/cursor.ts +2024 -508
- package/src/providers/devin-acp.d.ts +157 -0
- package/src/providers/devin-acp.ts +1103 -0
- package/src/providers/google-gemini-headers.d.ts +1 -1
- package/src/providers/google-gemini-headers.ts +1 -1
- package/src/providers/mock.ts +16 -1
- package/src/providers/openai-chat-server.ts +3 -3
- package/src/providers/openai-codex-responses.ts +27 -17
- package/src/providers/openai-responses-server.ts +5 -5
- package/src/providers/openai-responses-shared.d.ts +21 -1
- package/src/providers/openai-responses-shared.ts +60 -6
- package/src/providers/openai-responses.ts +10 -1
- package/src/providers/register-builtins.d.ts +1 -0
- package/src/providers/register-builtins.ts +21 -1
- package/src/stream.ts +14 -0
- package/src/types.d.ts +52 -11
- package/src/types.ts +70 -8
- package/src/utils/block-symbols.d.ts +15 -5
- package/src/utils/block-symbols.ts +16 -6
- package/src/utils/discovery/cursor.ts +3 -2
- package/src/utils/fallback-transport.d.ts +4 -1
- package/src/utils/fallback-transport.ts +12 -5
- package/src/utils.d.ts +13 -0
- package/src/utils.ts +17 -0
- package/dist/types/utils/codex-entitlement.d.ts +0 -22
- package/src/utils/codex-entitlement.d.ts +0 -22
- package/src/utils/codex-entitlement.ts +0 -57
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
|
|
7
7
|
export declare const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
|
|
8
|
-
export declare const DEFAULT_GEMINI_CLI_VERSION = "0.
|
|
8
|
+
export declare const DEFAULT_GEMINI_CLI_VERSION = "0.60.0";
|
|
9
9
|
export declare function getGeminiCliUserAgent(modelId?: string): string;
|
|
10
10
|
export declare const getGeminiCliHeaders: (modelId?: string) => {
|
|
11
11
|
"User-Agent": string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
|
|
7
7
|
export const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
|
|
8
|
-
export const DEFAULT_GEMINI_CLI_VERSION = "0.
|
|
8
|
+
export const DEFAULT_GEMINI_CLI_VERSION = "0.60.0";
|
|
9
9
|
|
|
10
10
|
export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): string {
|
|
11
11
|
const version =
|
package/src/providers/mock.ts
CHANGED
|
@@ -98,6 +98,8 @@ export interface MockResponse {
|
|
|
98
98
|
providerPayload?: AssistantMessage["providerPayload"];
|
|
99
99
|
/** Optional typed provider failure metadata for retry/fallback tests. */
|
|
100
100
|
transportFailure?: AssistantMessage["transportFailure"];
|
|
101
|
+
/** Bounded, redaction-safe failure classifier copied onto the terminal error message. */
|
|
102
|
+
errorCode?: string;
|
|
101
103
|
/** If set, the stream emits a terminal error event instead of completing. */
|
|
102
104
|
throw?: string | Error;
|
|
103
105
|
/** Delay before any event is emitted. Honors the call's AbortSignal. */
|
|
@@ -363,7 +365,7 @@ async function runMock(
|
|
|
363
365
|
: response.throw instanceof Error
|
|
364
366
|
? response.throw.message
|
|
365
367
|
: String(response.throw);
|
|
366
|
-
emitTerminalError(stream, model, startedAt, "error", message);
|
|
368
|
+
emitTerminalError(stream, model, startedAt, "error", message, response.errorCode ?? errorCodeOf(response.throw));
|
|
367
369
|
return;
|
|
368
370
|
}
|
|
369
371
|
|
|
@@ -485,6 +487,7 @@ function emitTerminalError(
|
|
|
485
487
|
startedAt: number,
|
|
486
488
|
reason: "aborted" | "error",
|
|
487
489
|
message: string,
|
|
490
|
+
errorCode?: string,
|
|
488
491
|
): void {
|
|
489
492
|
const failure: AssistantMessage = {
|
|
490
493
|
role: "assistant",
|
|
@@ -495,6 +498,7 @@ function emitTerminalError(
|
|
|
495
498
|
usage: emptyUsage(),
|
|
496
499
|
stopReason: reason as StopReason,
|
|
497
500
|
errorMessage: message,
|
|
501
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
498
502
|
timestamp: startedAt,
|
|
499
503
|
duration: Date.now() - startedAt,
|
|
500
504
|
};
|
|
@@ -502,6 +506,17 @@ function emitTerminalError(
|
|
|
502
506
|
stream.push({ type: "error", reason, error: failure });
|
|
503
507
|
}
|
|
504
508
|
|
|
509
|
+
/** Bounded safe-token classifier read off a thrown mock error, when present. */
|
|
510
|
+
function errorCodeOf(value: unknown): string | undefined {
|
|
511
|
+
try {
|
|
512
|
+
if (!(value instanceof Error)) return undefined;
|
|
513
|
+
const code = (value as { code?: unknown }).code;
|
|
514
|
+
return typeof code === "string" && code.length <= 64 && /^[A-Za-z0-9._-]+$/.test(code) ? code : undefined;
|
|
515
|
+
} catch {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
505
520
|
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
506
521
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
507
522
|
if (signal?.aborted) {
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
ToolResultMessage,
|
|
21
21
|
TSchema,
|
|
22
22
|
} from "../types";
|
|
23
|
-
import { sanitizeJsonStrings } from "../utils";
|
|
23
|
+
import { sanitizeJsonStrings, wireToolCallId } from "../utils";
|
|
24
24
|
import {
|
|
25
25
|
type OpenAIChatContentPart,
|
|
26
26
|
type OpenAIChatMessage,
|
|
@@ -377,7 +377,7 @@ export function encodeResponse(message: AssistantMessage, requestedModelId: stri
|
|
|
377
377
|
}
|
|
378
378
|
if (toolCalls.length > 0) {
|
|
379
379
|
responseMessage.tool_calls = toolCalls.map(tc => ({
|
|
380
|
-
id: tc.id,
|
|
380
|
+
id: wireToolCallId(tc.id),
|
|
381
381
|
type: "function",
|
|
382
382
|
function: { name: tc.name, arguments: stringifyArgs(tc.arguments) },
|
|
383
383
|
}));
|
|
@@ -639,7 +639,7 @@ export function encodeStream(
|
|
|
639
639
|
tool_calls: [
|
|
640
640
|
{
|
|
641
641
|
index: idx,
|
|
642
|
-
id: call
|
|
642
|
+
id: call ? wireToolCallId(call.id) : "",
|
|
643
643
|
type: "function",
|
|
644
644
|
function: { name: call?.name ?? "", arguments: "" },
|
|
645
645
|
},
|
|
@@ -51,10 +51,6 @@ import {
|
|
|
51
51
|
normalizeSystemPrompts,
|
|
52
52
|
sanitizeOpenAIResponsesHistoryItemsForReplay,
|
|
53
53
|
} from "../utils";
|
|
54
|
-
import {
|
|
55
|
-
formatOpenAICodexChatGPTEntitlementError,
|
|
56
|
-
isOpenAICodexChatGPTEntitlementError,
|
|
57
|
-
} from "../utils/codex-entitlement";
|
|
58
54
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
59
55
|
import { STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE, transportFailureFacts } from "../utils/fallback-transport";
|
|
60
56
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
@@ -134,7 +130,7 @@ const CODEX_PREVIOUS_RESPONSE_STALE_CODES = new Set(["previous_response_not_foun
|
|
|
134
130
|
// on both sides of the anchor phrase), since a deterministic history fault such
|
|
135
131
|
// as `Previous response's tool call ID is malformed.` must stay fatal:
|
|
136
132
|
// replaying it re-sends the same offending item.
|
|
137
|
-
const CODEX_PREVIOUS_RESPONSE_ID_TOKEN =
|
|
133
|
+
const CODEX_PREVIOUS_RESPONSE_ID_TOKEN = `previous[ _-]response[ _-]id`;
|
|
138
134
|
// Prose anchor reference ("Previous response with id 'resp_1' not found."):
|
|
139
135
|
// the canonical `previous_response_not_found` wording, which codex-lb re-codes
|
|
140
136
|
// as `invalid_request_error` the same way it re-codes the anchor-expiry fault
|
|
@@ -146,7 +142,7 @@ const CODEX_PREVIOUS_RESPONSE_PROSE_TOKEN = `previous[ _-]response`;
|
|
|
146
142
|
// the same offending item. Tempering the qualifier⇄token scan against these
|
|
147
143
|
// tokens keeps them fatal while pure anchor-stale prose still matches.
|
|
148
144
|
const CODEX_PREVIOUS_RESPONSE_STALE_SUBFIELD_GUARD = `tool[ _]calls?|function[ _]calls?|custom[ _]tools?|call[ _-]?ids?|message[ _]?ids?|items?\\b|output[ _-]?items?`;
|
|
149
|
-
const CODEX_ANCHOR_STALE_QUALIFIER =
|
|
145
|
+
const CODEX_ANCHOR_STALE_QUALIFIER = `invalid|expired|unknown|stale|not[ _-]?found|no longer`;
|
|
150
146
|
const CODEX_PREVIOUS_RESPONSE_STALE_MESSAGE = new RegExp(
|
|
151
147
|
`(?:${CODEX_ANCHOR_STALE_QUALIFIER})[^\\n]{0,48}?${CODEX_PREVIOUS_RESPONSE_ID_TOKEN}` +
|
|
152
148
|
`|${CODEX_PREVIOUS_RESPONSE_ID_TOKEN}[^\\n]{0,48}?(?:${CODEX_ANCHOR_STALE_QUALIFIER})`,
|
|
@@ -178,6 +174,7 @@ const CODEX_NON_RETRYABLE_EVENT_MESSAGE =
|
|
|
178
174
|
/invalid[_ -]function[_ -]parameters|invalid schema for function|invalid[_ -]tool[_ -]schema|schema must have type ["']?object["']?|request blocked[^\n]*invalid[_ -]prompt|code=invalid[_ -]prompt/i;
|
|
179
175
|
const CODEX_RETRYABLE_EVENT_MESSAGE =
|
|
180
176
|
/processing your request|retry your request|temporar(?:y|ily)|overloaded|service.?unavailable|internal error|server error/i;
|
|
177
|
+
const CODEX_ACCOUNT_MODEL_UNAVAILABLE_MESSAGE = /\bnot supported when using codex with a chatgpt account\b/i;
|
|
181
178
|
const CODEX_PROVIDER_SESSION_STATE_KEY = "openai-codex-responses";
|
|
182
179
|
const X_CODEX_TURN_STATE_HEADER = "x-codex-turn-state";
|
|
183
180
|
const X_MODELS_ETAG_HEADER = "x-models-etag";
|
|
@@ -1246,7 +1243,7 @@ function handleCodexStreamEvent(args: {
|
|
|
1246
1243
|
}
|
|
1247
1244
|
|
|
1248
1245
|
if (eventType === "error" || eventType === "response.failed") {
|
|
1249
|
-
throw createCodexProviderStreamError(rawEvent
|
|
1246
|
+
throw createCodexProviderStreamError(rawEvent);
|
|
1250
1247
|
}
|
|
1251
1248
|
|
|
1252
1249
|
return firstTokenTime;
|
|
@@ -2865,14 +2862,13 @@ async function openCodexSseEventStream(
|
|
|
2865
2862
|
updateCodexSessionMetadataFromHeaders(state, response.headers);
|
|
2866
2863
|
if (!response.ok) {
|
|
2867
2864
|
const info = await parseCodexError(response);
|
|
2868
|
-
const error = new Error(
|
|
2869
|
-
isOpenAICodexChatGPTEntitlementError(info.message, info.code)
|
|
2870
|
-
? formatOpenAICodexChatGPTEntitlementError(body.model)
|
|
2871
|
-
: info.friendlyMessage || info.message,
|
|
2872
|
-
);
|
|
2865
|
+
const error = new Error(info.friendlyMessage || info.message);
|
|
2873
2866
|
(error as { headers?: Headers; status?: number }).headers = response.headers;
|
|
2874
2867
|
(error as { headers?: Headers; status?: number }).status = response.status;
|
|
2875
2868
|
(error as { code?: string }).code = info.code;
|
|
2869
|
+
if (isCodexAccountModelUnavailable(info.message, info.code)) {
|
|
2870
|
+
(error as { credentialModelUnavailable?: true }).credentialModelUnavailable = true;
|
|
2871
|
+
}
|
|
2876
2872
|
throw error;
|
|
2877
2873
|
}
|
|
2878
2874
|
if (!response.body) {
|
|
@@ -3219,6 +3215,7 @@ function getCodexEventErrorMessage(rawEvent: Record<string, unknown>): string {
|
|
|
3219
3215
|
class CodexProviderStreamError extends Error {
|
|
3220
3216
|
readonly retryable: boolean;
|
|
3221
3217
|
readonly code?: string;
|
|
3218
|
+
readonly credentialModelUnavailable: boolean;
|
|
3222
3219
|
/**
|
|
3223
3220
|
* Provider-supplied message, before display formatting appends `code=`/`status=`
|
|
3224
3221
|
* metadata. Classification must read this, never `message`: the formatted string
|
|
@@ -3227,12 +3224,19 @@ class CodexProviderStreamError extends Error {
|
|
|
3227
3224
|
*/
|
|
3228
3225
|
readonly providerMessage: string;
|
|
3229
3226
|
|
|
3230
|
-
constructor(
|
|
3227
|
+
constructor(
|
|
3228
|
+
message: string,
|
|
3229
|
+
retryable: boolean,
|
|
3230
|
+
code: string | undefined,
|
|
3231
|
+
providerMessage: string,
|
|
3232
|
+
credentialModelUnavailable: boolean,
|
|
3233
|
+
) {
|
|
3231
3234
|
super(message);
|
|
3232
3235
|
this.name = "CodexProviderStreamError";
|
|
3233
3236
|
this.retryable = retryable;
|
|
3234
3237
|
this.code = code;
|
|
3235
3238
|
this.providerMessage = providerMessage;
|
|
3239
|
+
this.credentialModelUnavailable = credentialModelUnavailable;
|
|
3236
3240
|
}
|
|
3237
3241
|
}
|
|
3238
3242
|
|
|
@@ -3251,12 +3255,11 @@ function isRetryableCodexFailureEvent(rawEvent: Record<string, unknown>): boolea
|
|
|
3251
3255
|
return !!message && CODEX_RETRYABLE_EVENT_MESSAGE.test(message);
|
|
3252
3256
|
}
|
|
3253
3257
|
|
|
3254
|
-
function createCodexProviderStreamError(rawEvent: Record<string, unknown
|
|
3258
|
+
function createCodexProviderStreamError(rawEvent: Record<string, unknown>): CodexProviderStreamError {
|
|
3255
3259
|
const code = getCodexEventErrorCode(rawEvent);
|
|
3256
3260
|
const message = getCodexEventErrorMessage(rawEvent);
|
|
3257
|
-
const formattedMessage =
|
|
3258
|
-
|
|
3259
|
-
: typeof rawEvent.type === "string" && rawEvent.type === "error"
|
|
3261
|
+
const formattedMessage =
|
|
3262
|
+
typeof rawEvent.type === "string" && rawEvent.type === "error"
|
|
3260
3263
|
? formatCodexErrorEvent(rawEvent, code, message)
|
|
3261
3264
|
: (formatCodexFailure(rawEvent) ?? "Codex response failed");
|
|
3262
3265
|
return new CodexProviderStreamError(
|
|
@@ -3264,6 +3267,13 @@ function createCodexProviderStreamError(rawEvent: Record<string, unknown>, model
|
|
|
3264
3267
|
isRetryableCodexFailureEvent(rawEvent),
|
|
3265
3268
|
code || undefined,
|
|
3266
3269
|
message,
|
|
3270
|
+
isCodexAccountModelUnavailable(message, code),
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function isCodexAccountModelUnavailable(message: string | undefined, code: string | undefined): boolean {
|
|
3275
|
+
return (
|
|
3276
|
+
CODEX_ACCOUNT_MODEL_UNAVAILABLE_MESSAGE.test(message ?? "") && code?.toLowerCase() === "invalid_request_error"
|
|
3267
3277
|
);
|
|
3268
3278
|
}
|
|
3269
3279
|
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
Tool,
|
|
23
23
|
ToolCall,
|
|
24
24
|
} from "../types";
|
|
25
|
-
import { sanitizeJsonStrings } from "../utils";
|
|
25
|
+
import { sanitizeJsonStrings, wireToolCallId } from "../utils";
|
|
26
26
|
import {
|
|
27
27
|
type OpenAIResponsesFunctionCallItem,
|
|
28
28
|
type OpenAIResponsesFunctionCallOutputItem,
|
|
@@ -666,7 +666,7 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
|
|
|
666
666
|
out.push({
|
|
667
667
|
type: "custom_tool_call",
|
|
668
668
|
id: part.thoughtSignature ?? makeCustomCallId(),
|
|
669
|
-
call_id: part.id,
|
|
669
|
+
call_id: wireToolCallId(part.id),
|
|
670
670
|
name: part.customWireName,
|
|
671
671
|
input: rawInput,
|
|
672
672
|
status: "completed",
|
|
@@ -675,7 +675,7 @@ function buildOutputItems(message: AssistantMessage): OutputItem[] {
|
|
|
675
675
|
out.push({
|
|
676
676
|
type: "function_call",
|
|
677
677
|
id: part.thoughtSignature ?? makeFuncCallId(),
|
|
678
|
-
call_id: part.id,
|
|
678
|
+
call_id: wireToolCallId(part.id),
|
|
679
679
|
name: part.name,
|
|
680
680
|
arguments: JSON.stringify(sanitizeJsonStrings(part.arguments ?? {})),
|
|
681
681
|
status: "completed",
|
|
@@ -850,7 +850,7 @@ export function encodeStream(
|
|
|
850
850
|
: undefined;
|
|
851
851
|
const isCustom = customWireName !== undefined;
|
|
852
852
|
const itemId = tc?.thoughtSignature ?? (isCustom ? makeCustomCallId() : makeFuncCallId());
|
|
853
|
-
const callId = tc
|
|
853
|
+
const callId = tc ? wireToolCallId(tc.id) : "";
|
|
854
854
|
const name = customWireName ?? tc?.name ?? "";
|
|
855
855
|
const item = isCustom
|
|
856
856
|
? {
|
|
@@ -1143,7 +1143,7 @@ export function encodeStream(
|
|
|
1143
1143
|
const tc = ev.toolCall;
|
|
1144
1144
|
if (tc.customWireName && !cur.customWireName) cur.customWireName = tc.customWireName;
|
|
1145
1145
|
if (tc.thoughtSignature) cur.itemId = tc.thoughtSignature;
|
|
1146
|
-
cur.callId = tc.id;
|
|
1146
|
+
cur.callId = wireToolCallId(tc.id);
|
|
1147
1147
|
cur.name = cur.customWireName ?? tc.name;
|
|
1148
1148
|
if (cur.customWireName) {
|
|
1149
1149
|
// Custom tool: raw input string. Streamed deltas accumulated
|
|
@@ -44,7 +44,27 @@ export interface ProcessResponsesStreamOptions {
|
|
|
44
44
|
onFirstToken?: () => void;
|
|
45
45
|
onOutputItemDone?: (item: ResponseOutputItem) => void;
|
|
46
46
|
}
|
|
47
|
-
export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<
|
|
47
|
+
export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: ProcessResponsesStreamOptions): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Bounded classifier for a Responses stream that ended in failure before a
|
|
50
|
+
* successful terminal event: a `response.failed` envelope, a `response.completed`
|
|
51
|
+
* with `failed` status, a top-level `error` event, or an unexpected EOF (the
|
|
52
|
+
* stream returning without any terminal event). It is deliberately NOT a
|
|
53
|
+
* transport-retry fact: `transportFailureFacts` does not admit it, so preserving
|
|
54
|
+
* this diagnostic can never authorize a replay.
|
|
55
|
+
*/
|
|
56
|
+
export declare const RESPONSES_STREAM_FAILURE_CODE = "upstream_stream_interrupted";
|
|
57
|
+
/**
|
|
58
|
+
* Typed error for an SSE stream that ended (EOF) before any terminal event, so
|
|
59
|
+
* the caller sees a bounded classifier instead of a content-free success.
|
|
60
|
+
*/
|
|
61
|
+
export declare function unexpectedResponsesStreamEndError(): Error;
|
|
62
|
+
/**
|
|
63
|
+
* Read the bounded stream-failure classifier back off a thrown provider error.
|
|
64
|
+
* Only this module's own classifier is returned, so a foreign error's arbitrary
|
|
65
|
+
* `code` can never be forwarded onto the assistant message.
|
|
66
|
+
*/
|
|
67
|
+
export declare function responsesStreamFailureCode(error: unknown): string | undefined;
|
|
48
68
|
/**
|
|
49
69
|
* Mark tool-call blocks left incomplete by a length-truncated response so the
|
|
50
70
|
* agent loop rejects them instead of executing a best-effort partial parse.
|
|
@@ -52,6 +52,7 @@ const OPENAI_RESPONSES_PROGRESS_EVENT_TYPES = new Set([
|
|
|
52
52
|
"response.custom_tool_call_input.done",
|
|
53
53
|
"response.output_item.done",
|
|
54
54
|
"response.completed",
|
|
55
|
+
"response.incomplete",
|
|
55
56
|
"response.failed",
|
|
56
57
|
"error",
|
|
57
58
|
]);
|
|
@@ -447,7 +448,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
447
448
|
stream: AssistantMessageEventStream,
|
|
448
449
|
model: Model<TApi>,
|
|
449
450
|
options?: ProcessResponsesStreamOptions,
|
|
450
|
-
): Promise<
|
|
451
|
+
): Promise<boolean> {
|
|
451
452
|
type StreamItem = ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
|
|
452
453
|
type StreamBlock = ThinkingContent | TextContent | (ToolCall & { partialJson: string });
|
|
453
454
|
interface ItemEntry {
|
|
@@ -592,6 +593,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
592
593
|
};
|
|
593
594
|
let sawFirstToken = false;
|
|
594
595
|
|
|
596
|
+
let sawTerminalEvent = false;
|
|
595
597
|
for await (const event of openaiStream) {
|
|
596
598
|
if (event.type === "response.created") {
|
|
597
599
|
output.responseId = event.response.id;
|
|
@@ -963,14 +965,25 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
963
965
|
dropEntry(item.id, event.output_index, item.call_id);
|
|
964
966
|
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
965
967
|
}
|
|
966
|
-
} else if (event.type === "response.completed") {
|
|
968
|
+
} else if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
969
|
+
sawTerminalEvent = true;
|
|
967
970
|
const response = event.response;
|
|
968
971
|
if (response?.id) {
|
|
969
972
|
output.responseId = response.id;
|
|
970
973
|
}
|
|
971
974
|
populateResponsesUsageFromResponse(output, response?.usage);
|
|
972
975
|
calculateCost(model, output.usage);
|
|
973
|
-
|
|
976
|
+
// A `response.incomplete` frame is terminal because the response was cut
|
|
977
|
+
// short, so the event type itself proves truncation. Deriving `length`
|
|
978
|
+
// from the event rather than trusting `response.status` stops a relay that
|
|
979
|
+
// drops or rewrites the status from turning a truncated turn into a plain
|
|
980
|
+
// `stop`/`toolUse` that would dispatch a tool call carrying repaired
|
|
981
|
+
// partial arguments with no `incompleteArguments` flag.
|
|
982
|
+
const terminalStatus = response?.status;
|
|
983
|
+
output.stopReason =
|
|
984
|
+
event.type === "response.incomplete" && terminalStatus !== "failed" && terminalStatus !== "cancelled"
|
|
985
|
+
? "length"
|
|
986
|
+
: mapOpenAIResponsesStopReason(terminalStatus);
|
|
974
987
|
if (response?.status === "failed" || response?.status === "cancelled") {
|
|
975
988
|
const error = response?.error ?? (response as any)?.status_details?.error;
|
|
976
989
|
const details = response?.incomplete_details;
|
|
@@ -998,8 +1011,9 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
998
1011
|
output.stopReason = "toolUse";
|
|
999
1012
|
}
|
|
1000
1013
|
} else if (event.type === "error") {
|
|
1001
|
-
throw
|
|
1014
|
+
throw createResponsesStreamFailureError(`Error Code ${event.code}: ${event.message}` || "Unknown error");
|
|
1002
1015
|
} else if (event.type === "response.failed") {
|
|
1016
|
+
sawTerminalEvent = true;
|
|
1003
1017
|
const error = event.response?.error ?? (event.response as any)?.status_details?.error;
|
|
1004
1018
|
const details = event.response?.incomplete_details;
|
|
1005
1019
|
const message = error
|
|
@@ -1010,6 +1024,45 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
1010
1024
|
throw createResponsesFailedError(message, error?.code);
|
|
1011
1025
|
}
|
|
1012
1026
|
}
|
|
1027
|
+
return sawTerminalEvent;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Bounded classifier for a Responses stream that ended in failure before a
|
|
1032
|
+
* successful terminal event: a `response.failed` envelope, a `response.completed`
|
|
1033
|
+
* with `failed` status, a top-level `error` event, or an unexpected EOF (the
|
|
1034
|
+
* stream returning without any terminal event). It is deliberately NOT a
|
|
1035
|
+
* transport-retry fact: `transportFailureFacts` does not admit it, so preserving
|
|
1036
|
+
* this diagnostic can never authorize a replay.
|
|
1037
|
+
*/
|
|
1038
|
+
export const RESPONSES_STREAM_FAILURE_CODE = "upstream_stream_interrupted";
|
|
1039
|
+
|
|
1040
|
+
/** Attach the bounded stream-failure classifier without touching retry facts. */
|
|
1041
|
+
function createResponsesStreamFailureError(message: string): Error {
|
|
1042
|
+
return Object.assign(new Error(message), { code: RESPONSES_STREAM_FAILURE_CODE });
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Typed error for an SSE stream that ended (EOF) before any terminal event, so
|
|
1047
|
+
* the caller sees a bounded classifier instead of a content-free success.
|
|
1048
|
+
*/
|
|
1049
|
+
export function unexpectedResponsesStreamEndError(): Error {
|
|
1050
|
+
return createResponsesStreamFailureError("Upstream response stream ended before a terminal response event");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Read the bounded stream-failure classifier back off a thrown provider error.
|
|
1055
|
+
* Only this module's own classifier is returned, so a foreign error's arbitrary
|
|
1056
|
+
* `code` can never be forwarded onto the assistant message.
|
|
1057
|
+
*/
|
|
1058
|
+
export function responsesStreamFailureCode(error: unknown): string | undefined {
|
|
1059
|
+
try {
|
|
1060
|
+
return (error as { code?: unknown } | undefined)?.code === RESPONSES_STREAM_FAILURE_CODE
|
|
1061
|
+
? RESPONSES_STREAM_FAILURE_CODE
|
|
1062
|
+
: undefined;
|
|
1063
|
+
} catch {
|
|
1064
|
+
return undefined;
|
|
1065
|
+
}
|
|
1013
1066
|
}
|
|
1014
1067
|
|
|
1015
1068
|
/**
|
|
@@ -1020,10 +1073,11 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
1020
1073
|
* Exactly OpenAI's capacity-overload code is carried through as transport facts,
|
|
1021
1074
|
* matched case-sensitively; every other failure stays a plain error, so an
|
|
1022
1075
|
* untyped, cased, or malformed code can never reach a typed retry admission. The
|
|
1023
|
-
* display message is unchanged either way.
|
|
1076
|
+
* display message is unchanged either way. Every non-overload envelope carries
|
|
1077
|
+
* the bounded stream-failure classifier instead of a typed retry fact.
|
|
1024
1078
|
*/
|
|
1025
1079
|
function createResponsesFailedError(message: string, code: string | undefined): Error {
|
|
1026
|
-
if (code !== SERVER_OVERLOADED_PROVIDER_CODE) return
|
|
1080
|
+
if (code !== SERVER_OVERLOADED_PROVIDER_CODE) return createResponsesStreamFailureError(message);
|
|
1027
1081
|
const error = new Error(message) as Error & { openaiErrorCode?: string };
|
|
1028
1082
|
error.openaiErrorCode = SERVER_OVERLOADED_PROVIDER_CODE;
|
|
1029
1083
|
return error;
|
|
@@ -92,6 +92,8 @@ import {
|
|
|
92
92
|
normalizeResponsesToolCallIdForTransform,
|
|
93
93
|
processResponsesStream,
|
|
94
94
|
repairOrphanResponsesToolOutputs,
|
|
95
|
+
responsesStreamFailureCode,
|
|
96
|
+
unexpectedResponsesStreamEndError,
|
|
95
97
|
} from "./openai-responses-shared";
|
|
96
98
|
import {
|
|
97
99
|
applyOpenCodeGoSessionHeader,
|
|
@@ -468,7 +470,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
468
470
|
stream.push({ type: "start", partial: output });
|
|
469
471
|
|
|
470
472
|
const nativeOutputItems: Array<Record<string, unknown>> = [];
|
|
471
|
-
await processResponsesStream(
|
|
473
|
+
const sawTerminalEvent = await processResponsesStream(
|
|
472
474
|
iterateWithIdleTimeout(openaiStream, {
|
|
473
475
|
idleTimeoutMs,
|
|
474
476
|
firstItemTimeoutMs: firstEventTimeoutMs,
|
|
@@ -505,6 +507,11 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
505
507
|
throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
506
508
|
}
|
|
507
509
|
|
|
510
|
+
// The Responses SSE contract always ends with a terminal event. A stream
|
|
511
|
+
// that closes without one is an interrupted upstream, not a completed
|
|
512
|
+
// turn, and must not surface as a content-free success.
|
|
513
|
+
if (!sawTerminalEvent) throw unexpectedResponsesStreamEndError();
|
|
514
|
+
|
|
508
515
|
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
|
|
509
516
|
if (isOpenCodeGoEmptyCompletedResponse(model, output, nativeOutputItems.length)) {
|
|
510
517
|
output.stopReason = "error";
|
|
@@ -534,6 +541,8 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
534
541
|
output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
535
542
|
output.errorStatus = extractHttpStatusFromError(localAbortReason ?? normalizedError);
|
|
536
543
|
output.transportFailure = transportFailureFacts(localAbortReason ?? normalizedError);
|
|
544
|
+
const streamFailureCode = responsesStreamFailureCode(localAbortReason ?? normalizedError);
|
|
545
|
+
if (streamFailureCode !== undefined) output.errorCode = streamFailureCode;
|
|
537
546
|
output.errorMessage =
|
|
538
547
|
localAbortReason?.message ?? (await finalizeErrorMessage(normalizedError, rawRequestDump));
|
|
539
548
|
output.errorMessage = rewriteCopilotError(output.errorMessage, normalizedError, model.provider);
|
|
@@ -53,6 +53,7 @@ export declare const streamOpenAICodexResponses: (model: Model<"openai-codex-res
|
|
|
53
53
|
export declare const streamOpenAICompletions: (model: Model<"openai-completions">, context: Context, options: OptionsForApi<"openai-completions">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
54
54
|
export declare const streamOpenAIResponses: (model: Model<"openai-responses">, context: Context, options: OptionsForApi<"openai-responses">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
55
55
|
export declare const streamCursor: (model: Model<"cursor-agent">, context: Context, options: OptionsForApi<"cursor-agent">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
56
|
+
export declare const streamDevinAcp: (model: Model<"devin-acp">, context: Context, options: import("../types").StreamOptions, onStreamCreated?: () => void) => EventStreamImpl;
|
|
56
57
|
export declare const streamOllama: (model: Model<"ollama-chat">, context: Context, options: OptionsForApi<"ollama-chat">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
57
58
|
export declare const streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: OptionsForApi<"bedrock-converse-stream">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
58
59
|
export declare const streamKiroCodeWhisperer: (model: Model<"kiro-codewhisperer-stream">, context: Context, options: OptionsForApi<"kiro-codewhisperer-stream">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
@@ -34,6 +34,7 @@ import type { BedrockOptions } from "./amazon-bedrock";
|
|
|
34
34
|
import type { AnthropicOptions } from "./anthropic";
|
|
35
35
|
import type { AzureOpenAIResponsesOptions } from "./azure-openai-responses";
|
|
36
36
|
import type { CursorOptions } from "./cursor";
|
|
37
|
+
import type { DevinAcpOptions } from "./devin-acp";
|
|
37
38
|
import type { GoogleOptions } from "./google";
|
|
38
39
|
import type { GoogleGeminiCliOptions } from "./google-gemini-cli";
|
|
39
40
|
import type { GoogleVertexOptions } from "./google-vertex";
|
|
@@ -146,6 +147,14 @@ interface CursorProviderModule {
|
|
|
146
147
|
) => AssistantMessageEventStream;
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
interface DevinAcpProviderModule {
|
|
151
|
+
streamDevinAcp: (
|
|
152
|
+
model: Model<"devin-acp">,
|
|
153
|
+
context: Context,
|
|
154
|
+
options: DevinAcpOptions,
|
|
155
|
+
) => AssistantMessageEventStream;
|
|
156
|
+
}
|
|
157
|
+
|
|
149
158
|
interface BedrockProviderModule {
|
|
150
159
|
streamBedrock: (
|
|
151
160
|
model: Model<"bedrock-converse-stream">,
|
|
@@ -176,6 +185,7 @@ let openAICompletionsProviderModulePromise: Promise<LazyProviderModule<"openai-c
|
|
|
176
185
|
let openAIResponsesProviderModulePromise: Promise<LazyProviderModule<"openai-responses">> | undefined;
|
|
177
186
|
let ollamaProviderModulePromise: Promise<LazyProviderModule<"ollama-chat">> | undefined;
|
|
178
187
|
let cursorProviderModulePromise: Promise<LazyProviderModule<"cursor-agent">> | undefined;
|
|
188
|
+
let devinAcpProviderModulePromise: Promise<LazyProviderModule<"devin-acp">> | undefined;
|
|
179
189
|
let bedrockProviderModuleOverride: LazyProviderModule<"bedrock-converse-stream"> | undefined;
|
|
180
190
|
let kiroCodeWhispererProviderModulePromise: Promise<LazyProviderModule<"kiro-codewhisperer-stream">> | undefined;
|
|
181
191
|
let bedrockProviderModulePromise: Promise<LazyProviderModule<"bedrock-converse-stream">> | undefined;
|
|
@@ -459,6 +469,14 @@ function loadCursorProviderModule(): Promise<LazyProviderModule<"cursor-agent">>
|
|
|
459
469
|
return cursorProviderModulePromise;
|
|
460
470
|
}
|
|
461
471
|
|
|
472
|
+
function loadDevinAcpProviderModule(): Promise<LazyProviderModule<"devin-acp">> {
|
|
473
|
+
devinAcpProviderModulePromise ||= Promise.resolve().then(() => {
|
|
474
|
+
const provider = require("./devin-acp") as DevinAcpProviderModule;
|
|
475
|
+
return { stream: provider.streamDevinAcp };
|
|
476
|
+
});
|
|
477
|
+
return devinAcpProviderModulePromise;
|
|
478
|
+
}
|
|
479
|
+
|
|
462
480
|
function loadBedrockProviderModule(): Promise<LazyProviderModule<"bedrock-converse-stream">> {
|
|
463
481
|
if (bedrockProviderModuleOverride) {
|
|
464
482
|
return Promise.resolve(bedrockProviderModuleOverride);
|
|
@@ -492,6 +510,7 @@ export const PROVIDER_RUNTIME_DESCRIPTORS: readonly ProviderRuntimeDescriptor<Ap
|
|
|
492
510
|
{ api: "openai-responses", load: loadOpenAIResponsesProviderModule },
|
|
493
511
|
{ api: "ollama-chat", load: loadOllamaProviderModule },
|
|
494
512
|
{ api: "cursor-agent", load: loadCursorProviderModule },
|
|
513
|
+
{ api: "devin-acp", load: loadDevinAcpProviderModule },
|
|
495
514
|
{ api: "kiro-codewhisperer-stream", load: loadKiroCodeWhispererProviderModule },
|
|
496
515
|
{ api: "bedrock-converse-stream", load: loadBedrockProviderModule },
|
|
497
516
|
] as readonly ErasedProviderRuntimeDescriptor[];
|
|
@@ -537,7 +556,8 @@ export const streamOpenAIResponses = createLazyStream(
|
|
|
537
556
|
loadOpenAIResponsesProviderModule,
|
|
538
557
|
PROVIDER_OWNED_STREAM_WATCHDOG,
|
|
539
558
|
);
|
|
540
|
-
export const streamCursor = createLazyStream(loadCursorProviderModule);
|
|
559
|
+
export const streamCursor = createLazyStream(loadCursorProviderModule, PROVIDER_OWNED_STREAM_WATCHDOG);
|
|
560
|
+
export const streamDevinAcp = createLazyStream(loadDevinAcpProviderModule, PROVIDER_OWNED_STREAM_WATCHDOG);
|
|
541
561
|
export const streamOllama = createLazyStream(loadOllamaProviderModule);
|
|
542
562
|
|
|
543
563
|
export const streamBedrock = createLazyStream(loadBedrockProviderModule);
|
package/src/stream.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
readAwsStaticEnvironmentCredentials,
|
|
40
40
|
} from "./providers/aws-credential-config";
|
|
41
41
|
import type { CursorOptions } from "./providers/cursor";
|
|
42
|
+
import type { DevinAcpOptions } from "./providers/devin-acp";
|
|
42
43
|
import type { GoogleOptions } from "./providers/google";
|
|
43
44
|
import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
|
|
44
45
|
import type { GoogleVertexOptions } from "./providers/google-vertex";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
streamAzureOpenAIResponses,
|
|
55
56
|
streamBedrock,
|
|
56
57
|
streamCursor,
|
|
58
|
+
streamDevinAcp,
|
|
57
59
|
streamGoogle,
|
|
58
60
|
streamGoogleGeminiCli,
|
|
59
61
|
streamGoogleVertex,
|
|
@@ -398,6 +400,9 @@ export function stream<TApi extends Api>(
|
|
|
398
400
|
(options || {}) as KiroCodeWhispererOptions,
|
|
399
401
|
onStreamCreated,
|
|
400
402
|
);
|
|
403
|
+
} else if (model.api === "devin-acp") {
|
|
404
|
+
// Devin authenticates through its own CLI, so there is no GJC API key to resolve.
|
|
405
|
+
return streamDevinAcp(model as Model<"devin-acp">, context, (options || {}) as DevinAcpOptions, onStreamCreated);
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
const apiKey = options?.apiKey || (model.provider === "opencodex" ? "local" : getEnvApiKey(model.provider));
|
|
@@ -944,12 +949,15 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
944
949
|
repetitionPenalty: options?.repetitionPenalty,
|
|
945
950
|
maxTokens: resolveDefaultRequestMaxTokens(model, options?.maxTokens),
|
|
946
951
|
signal: options?.signal,
|
|
952
|
+
streamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,
|
|
953
|
+
streamIdleTimeoutMs: options?.streamIdleTimeoutMs,
|
|
947
954
|
apiKey: apiKey || options?.apiKey,
|
|
948
955
|
fallbackManaged: options?.fallbackManaged,
|
|
949
956
|
fallbackAttempt: options?.fallbackAttempt,
|
|
950
957
|
cacheRetention: options?.cacheRetention ?? model.cacheRetention,
|
|
951
958
|
headers: options?.headers,
|
|
952
959
|
initiatorOverride: options?.initiatorOverride,
|
|
960
|
+
maintenanceCall: options?.maintenanceCall,
|
|
953
961
|
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
954
962
|
requestMaxRetries: options?.fallbackManaged ? 0 : options?.requestMaxRetries,
|
|
955
963
|
streamMaxRetries: options?.fallbackManaged ? 0 : options?.streamMaxRetries,
|
|
@@ -957,6 +965,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
957
965
|
sessionId: options?.sessionId,
|
|
958
966
|
providerSessionId: options?.providerSessionId,
|
|
959
967
|
providerSessionState: options?.providerSessionState,
|
|
968
|
+
devinAcp: options?.devinAcp,
|
|
960
969
|
onPayload: options?.onPayload,
|
|
961
970
|
onResponse: options?.onResponse,
|
|
962
971
|
onStreamCreated: options?.onStreamCreated,
|
|
@@ -1265,6 +1274,11 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1265
1274
|
});
|
|
1266
1275
|
}
|
|
1267
1276
|
|
|
1277
|
+
case "devin-acp":
|
|
1278
|
+
return castApi<"devin-acp">({
|
|
1279
|
+
...base,
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1268
1282
|
case "kiro-codewhisperer-stream":
|
|
1269
1283
|
return castApi<"kiro-codewhisperer-stream">({
|
|
1270
1284
|
...base,
|