@gajae-code/ai 0.18.1 → 0.18.6
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 +74 -0
- package/dist/types/auth-storage.d.ts +8 -1
- package/dist/types/index.d.ts +0 -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 +2 -0
- package/dist/types/types.d.ts +1 -1
- package/dist/types/utils/fallback-transport.d.ts +6 -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/index.ts +0 -1
- package/src/model-pricing.ts +10 -0
- package/src/model-thinking.ts +8 -0
- package/src/models.json +210 -0
- package/src/providers/anthropic.ts +25 -1
- 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/kiro-api-key.d.ts +1 -0
- package/src/providers/kiro-api-key.ts +1 -1
- package/src/providers/kiro-codewhisperer.ts +142 -34
- package/src/providers/openai-codex-responses.d.ts +2 -0
- package/src/providers/openai-codex-responses.ts +438 -40
- package/src/providers/openai-completions.ts +19 -1
- package/src/stream.ts +20 -0
- package/src/types.d.ts +1 -1
- package/src/types.ts +3 -0
- package/src/utils/fallback-transport.d.ts +6 -0
- package/src/utils/fallback-transport.ts +6 -0
- package/src/utils/idle-iterator.ts +10 -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
- package/dist/types/utils/h2-fetch.d.ts +0 -35
- package/src/utils/h2-fetch.ts +0 -133
|
@@ -26,11 +26,11 @@ import type {
|
|
|
26
26
|
ToolResultMessage,
|
|
27
27
|
} from "../types";
|
|
28
28
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
29
|
-
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
29
|
+
import { PROVIDER_PROTOCOL_MISMATCH_ERROR_CODE, transportFailureFacts } from "../utils/fallback-transport";
|
|
30
30
|
import { withHttpStatus } from "../utils/http-inspector";
|
|
31
31
|
import { captureUnicodeEscapeEvidence } from "../utils/json-parse";
|
|
32
32
|
import { decodeEventStream } from "./aws-eventstream";
|
|
33
|
-
import { isKiroApiKey, streamKiroApiKey, toKiroModelId } from "./kiro-api-key";
|
|
33
|
+
import { isKiroApiKey, sanitizeKiroError, streamKiroApiKey, toKiroModelId } from "./kiro-api-key";
|
|
34
34
|
|
|
35
35
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
36
|
// Provider options
|
|
@@ -58,11 +58,15 @@ interface WireToolSpec {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
interface WireToolResult {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
toolUseId: string;
|
|
62
|
+
status: "success" | "error";
|
|
63
|
+
content: Array<{ text: string }>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface WireToolUse {
|
|
67
|
+
toolUseId: string;
|
|
68
|
+
name: string;
|
|
69
|
+
input: Record<string, unknown>;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
interface WireUserMessage {
|
|
@@ -71,16 +75,17 @@ interface WireUserMessage {
|
|
|
71
75
|
modelId?: string;
|
|
72
76
|
userInputMessageContext?: {
|
|
73
77
|
tools?: { tools: WireToolSpec[] };
|
|
74
|
-
toolResults?:
|
|
78
|
+
toolResults?: WireToolResult[];
|
|
75
79
|
editorStateContext?: Record<string, unknown>;
|
|
76
80
|
};
|
|
77
|
-
origin
|
|
81
|
+
origin: typeof KIRO_ORIGIN;
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
interface WireAssistantMessage {
|
|
82
86
|
assistantResponseMessage: {
|
|
83
87
|
content: string;
|
|
88
|
+
toolUses?: WireToolUse[];
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -137,6 +142,12 @@ interface ErrorPayload {
|
|
|
137
142
|
|
|
138
143
|
const DEFAULT_REGION = "us-east-1";
|
|
139
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Without `origin` the service ignores `modelId` and routes every request to
|
|
147
|
+
* `auto`; with it, response frames report the requested model.
|
|
148
|
+
*/
|
|
149
|
+
const KIRO_ORIGIN = "AI_EDITOR";
|
|
150
|
+
|
|
140
151
|
type Block = (TextContent | ToolCall) & { index?: number; partialJson?: string };
|
|
141
152
|
|
|
142
153
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -235,14 +246,29 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
235
246
|
});
|
|
236
247
|
|
|
237
248
|
if (!response.ok) {
|
|
238
|
-
const errBody = await response
|
|
249
|
+
const errBody = await readBodyPrefix(response);
|
|
239
250
|
throw withHttpStatus(
|
|
240
|
-
new Error(`Kiro CodeWhisperer HTTP ${response.status}: ${errBody
|
|
251
|
+
new Error(sanitizeKiroError(`Kiro CodeWhisperer HTTP ${response.status}: ${errBody}`, bearerToken)),
|
|
241
252
|
response.status,
|
|
242
253
|
);
|
|
243
254
|
}
|
|
244
255
|
|
|
245
|
-
|
|
256
|
+
// Verify content-type is eventstream; if not, read and report the actual error
|
|
257
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
258
|
+
const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
259
|
+
if (!response.body || mediaType !== "application/vnd.amazon.eventstream") {
|
|
260
|
+
const errBody = await readBodyPrefix(response);
|
|
261
|
+
// Deliberately no HTTP status metadata and no "HTTP <code>" wording: a 2xx
|
|
262
|
+
// protocol mismatch is deterministic, and a status would materialize
|
|
263
|
+
// transport-failure facts that session fallback retries.
|
|
264
|
+
// Sanitize each untrusted part on its own so the fixed prefix does not eat
|
|
265
|
+
// into the body's 1000-character diagnostic budget.
|
|
266
|
+
const safeContentType = contentType ? sanitizeKiroError(contentType, bearerToken) : "";
|
|
267
|
+
const safeBody = errBody ? sanitizeKiroError(errBody, bearerToken) : "";
|
|
268
|
+
throw new KiroNonEventStreamError(
|
|
269
|
+
`Kiro CodeWhisperer returned a non-eventstream ${response.status} response (${safeContentType}): ${safeBody}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
246
272
|
|
|
247
273
|
// Decode eventstream
|
|
248
274
|
for await (const message of decodeEventStream(response.body)) {
|
|
@@ -343,8 +369,10 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
343
369
|
delete (block as Block).partialJson;
|
|
344
370
|
}
|
|
345
371
|
output.stopReason = options.signal?.aborted ? "aborted" : "error";
|
|
346
|
-
|
|
372
|
+
// The non-eventstream diagnostic embeds untrusted body text; never parse a status out of it.
|
|
373
|
+
output.errorStatus = error instanceof KiroNonEventStreamError ? undefined : extractHttpStatusFromError(error);
|
|
347
374
|
output.transportFailure = transportFailureFacts(error);
|
|
375
|
+
if (error instanceof KiroNonEventStreamError) output.errorCode = PROVIDER_PROTOCOL_MISMATCH_ERROR_CODE;
|
|
348
376
|
const baseMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
349
377
|
output.errorMessage = baseMessage;
|
|
350
378
|
output.duration = Date.now() - startTime;
|
|
@@ -381,14 +409,47 @@ function buildConversationState(
|
|
|
381
409
|
const history: WireHistoryMessage[] = [];
|
|
382
410
|
const systemPrompt = context.systemPrompt?.join("\n") ?? "";
|
|
383
411
|
|
|
384
|
-
|
|
412
|
+
// Parallel tool calls end the conversation with several consecutive toolResult
|
|
413
|
+
// messages. They all answer the last assistant turn, so they must travel
|
|
414
|
+
// together in currentMessage; leaving the earlier ones as a separate history
|
|
415
|
+
// entry is rejected upstream with TOOL_USE_RESULT_MISMATCH.
|
|
416
|
+
let currentStart = messages.length - 1;
|
|
417
|
+
while (
|
|
418
|
+
currentStart > 0 &&
|
|
419
|
+
messages[currentStart].role === "toolResult" &&
|
|
420
|
+
messages[currentStart - 1].role === "toolResult"
|
|
421
|
+
) {
|
|
422
|
+
currentStart--;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
for (let i = 0; i < currentStart; i++) {
|
|
385
426
|
const msg = messages[i];
|
|
427
|
+
const previous = history[history.length - 1];
|
|
428
|
+
// Parallel tool calls produce consecutive toolResult messages; they answer one
|
|
429
|
+
// assistant turn, so their results share a single user entry.
|
|
430
|
+
if (
|
|
431
|
+
msg.role === "toolResult" &&
|
|
432
|
+
messages[i - 1]?.role === "toolResult" &&
|
|
433
|
+
previous &&
|
|
434
|
+
"userInputMessage" in previous
|
|
435
|
+
) {
|
|
436
|
+
previous.userInputMessage.userInputMessageContext ??= {};
|
|
437
|
+
const context = previous.userInputMessage.userInputMessageContext;
|
|
438
|
+
context.toolResults ??= [];
|
|
439
|
+
context.toolResults.push(convertToolResult(msg as ToolResultMessage));
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
386
442
|
history.push(convertToWireMessage(msg, modelId, i === 0 ? systemPrompt : undefined));
|
|
387
443
|
}
|
|
388
444
|
|
|
389
|
-
// Convert the last message as currentMessage
|
|
445
|
+
// Convert the last message as currentMessage, carrying every trailing parallel result.
|
|
390
446
|
const lastMsg = messages[messages.length - 1];
|
|
391
447
|
const currentMessage = convertToWireUserMessage(lastMsg, modelId, systemPrompt);
|
|
448
|
+
if (currentStart < messages.length - 1) {
|
|
449
|
+
currentMessage.userInputMessage.userInputMessageContext = {
|
|
450
|
+
toolResults: messages.slice(currentStart).map(msg => convertToolResult(msg as ToolResultMessage)),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
392
453
|
|
|
393
454
|
// Add tools to the current message context
|
|
394
455
|
if (context.tools && context.tools.length > 0) {
|
|
@@ -419,20 +480,27 @@ function convertToWireMessage(
|
|
|
419
480
|
if (msg.role === "toolResult") {
|
|
420
481
|
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
421
482
|
}
|
|
422
|
-
// assistant → assistant response
|
|
483
|
+
// assistant → assistant response; tool calls stay structured so later tool
|
|
484
|
+
// results can reference the toolUseId they answer.
|
|
423
485
|
const textParts: string[] = [];
|
|
486
|
+
const toolUses: WireToolUse[] = [];
|
|
424
487
|
for (const block of msg.content) {
|
|
425
488
|
if (typeof block === "string") {
|
|
426
489
|
textParts.push(block);
|
|
427
490
|
} else if (block.type === "text") {
|
|
428
491
|
textParts.push(block.text);
|
|
429
492
|
} else if (block.type === "toolCall") {
|
|
430
|
-
|
|
493
|
+
toolUses.push({
|
|
494
|
+
toolUseId: block.id,
|
|
495
|
+
name: block.name,
|
|
496
|
+
input: (block.arguments ?? {}) as Record<string, unknown>,
|
|
497
|
+
});
|
|
431
498
|
}
|
|
432
499
|
}
|
|
433
500
|
return {
|
|
434
501
|
assistantResponseMessage: {
|
|
435
|
-
content: textParts.join("\n")
|
|
502
|
+
content: textParts.join("\n"),
|
|
503
|
+
...(toolUses.length > 0 ? { toolUses } : {}),
|
|
436
504
|
},
|
|
437
505
|
};
|
|
438
506
|
}
|
|
@@ -442,7 +510,9 @@ function convertToWireUserMessage(
|
|
|
442
510
|
modelId: string,
|
|
443
511
|
systemPrompt?: string,
|
|
444
512
|
): WireUserMessage {
|
|
445
|
-
|
|
513
|
+
// A tool result travels in userInputMessageContext.toolResults; repeating it as
|
|
514
|
+
// message text would show the model each result twice.
|
|
515
|
+
let content = msg.role === "toolResult" ? "Tool results provided." : extractTextContent(msg);
|
|
446
516
|
if (systemPrompt) {
|
|
447
517
|
content = `${systemPrompt}\n\n${content}`;
|
|
448
518
|
}
|
|
@@ -451,30 +521,32 @@ function convertToWireUserMessage(
|
|
|
451
521
|
userInputMessage: {
|
|
452
522
|
content,
|
|
453
523
|
modelId,
|
|
524
|
+
origin: KIRO_ORIGIN,
|
|
454
525
|
},
|
|
455
526
|
};
|
|
456
527
|
|
|
457
|
-
// Handle tool results
|
|
458
528
|
if (msg.role === "toolResult") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
content: detail.type === "text" ? detail.text : "",
|
|
463
|
-
toolUseId: toolResultMsg.toolCallId,
|
|
464
|
-
status: toolResultMsg.isError ? ("error" as const) : ("success" as const),
|
|
465
|
-
},
|
|
466
|
-
}));
|
|
467
|
-
if (toolResults.length > 0) {
|
|
468
|
-
if (!userMsg.userInputMessage.userInputMessageContext) {
|
|
469
|
-
userMsg.userInputMessage.userInputMessageContext = {};
|
|
470
|
-
}
|
|
471
|
-
userMsg.userInputMessage.userInputMessageContext.toolResults = { toolResults: [toolResults] };
|
|
472
|
-
}
|
|
529
|
+
userMsg.userInputMessage.userInputMessageContext = {
|
|
530
|
+
toolResults: [convertToolResult(msg as ToolResultMessage)],
|
|
531
|
+
};
|
|
473
532
|
}
|
|
474
533
|
|
|
475
534
|
return userMsg;
|
|
476
535
|
}
|
|
477
536
|
|
|
537
|
+
/** One CodeWhisperer tool result per tool call, keyed by the toolUseId it answers. */
|
|
538
|
+
function convertToolResult(msg: ToolResultMessage): WireToolResult {
|
|
539
|
+
const text = (msg.content ?? [])
|
|
540
|
+
.map(block => (block.type === "text" ? block.text : block.type === "image" ? "[image omitted]" : ""))
|
|
541
|
+
.filter(part => part.length > 0)
|
|
542
|
+
.join("\n");
|
|
543
|
+
return {
|
|
544
|
+
toolUseId: msg.toolCallId,
|
|
545
|
+
status: msg.isError ? "error" : "success",
|
|
546
|
+
content: [{ text }],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
478
550
|
function extractTextContent(msg: Context["messages"][number]): string {
|
|
479
551
|
if (typeof msg.content === "string") return msg.content;
|
|
480
552
|
if (Array.isArray(msg.content)) {
|
|
@@ -573,6 +645,42 @@ function handleToolUseEvent(
|
|
|
573
645
|
// Helpers
|
|
574
646
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
575
647
|
|
|
648
|
+
/** A 2xx response that is not an AWS event stream: a deterministic protocol error, never a transport failure. */
|
|
649
|
+
class KiroNonEventStreamError extends Error {
|
|
650
|
+
override name = "KiroNonEventStreamError";
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Byte cap for diagnostic error bodies; the rest of the stream is cancelled unread. */
|
|
654
|
+
const ERROR_BODY_PREFIX_BYTES = 4096;
|
|
655
|
+
|
|
656
|
+
/** Read at most {@link ERROR_BODY_PREFIX_BYTES} of a response body for diagnostics, then cancel the remainder. */
|
|
657
|
+
async function readBodyPrefix(response: Response): Promise<string> {
|
|
658
|
+
if (!response.body) return "";
|
|
659
|
+
const reader = response.body.getReader();
|
|
660
|
+
const chunks: Uint8Array[] = [];
|
|
661
|
+
let total = 0;
|
|
662
|
+
try {
|
|
663
|
+
while (total < ERROR_BODY_PREFIX_BYTES) {
|
|
664
|
+
const { done, value } = await reader.read();
|
|
665
|
+
if (done) break;
|
|
666
|
+
const take = value.subarray(0, ERROR_BODY_PREFIX_BYTES - total);
|
|
667
|
+
chunks.push(take);
|
|
668
|
+
total += take.length;
|
|
669
|
+
}
|
|
670
|
+
} catch {
|
|
671
|
+
// Diagnostic read only; report whatever prefix arrived.
|
|
672
|
+
} finally {
|
|
673
|
+
reader.cancel().catch(() => {});
|
|
674
|
+
}
|
|
675
|
+
const bytes = new Uint8Array(total);
|
|
676
|
+
let offset = 0;
|
|
677
|
+
for (const chunk of chunks) {
|
|
678
|
+
bytes.set(chunk, offset);
|
|
679
|
+
offset += chunk.length;
|
|
680
|
+
}
|
|
681
|
+
return new TextDecoder().decode(bytes);
|
|
682
|
+
}
|
|
683
|
+
|
|
576
684
|
function resolveBearerToken(apiKey: string | undefined): string | undefined {
|
|
577
685
|
if (!apiKey) {
|
|
578
686
|
return $credentialEnv("KIRO_API_KEY") ?? $credentialEnv("AWS_BEARER_TOKEN_KIRO") ?? undefined;
|
|
@@ -11,6 +11,8 @@ export interface OpenAICodexResponsesOptions extends StreamOptions {
|
|
|
11
11
|
preferWebsockets?: boolean;
|
|
12
12
|
serviceTier?: ServiceTier;
|
|
13
13
|
}
|
|
14
|
+
/** @internal Exported for tests. */
|
|
15
|
+
export declare function createCodexStreamProgressClassifier(): (event: unknown) => boolean;
|
|
14
16
|
type CodexTransport = "sse" | "websocket";
|
|
15
17
|
export interface OpenAICodexWebSocketDebugStats {
|
|
16
18
|
fullContextRequests: number;
|