@opencode-ai/ai 0.0.0-beta-18155 → 0.0.0-beta-18230
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/dist/image-client.d.ts +1 -1
- package/dist/image-client.js +10 -1
- package/dist/protocols/anthropic-messages.d.ts +2 -21
- package/dist/protocols/anthropic-messages.js +55 -10
- package/dist/protocols/bedrock-converse.d.ts +16 -50
- package/dist/protocols/bedrock-converse.js +4 -15
- package/dist/protocols/bedrock-event-stream.js +1 -1
- package/dist/protocols/open-responses-continuation.js +6 -1
- package/dist/protocols/open-responses.d.ts +23 -2
- package/dist/protocols/open-responses.js +31 -17
- package/dist/protocols/openai-chat.d.ts +1 -0
- package/dist/protocols/openai-chat.js +32 -8
- package/dist/protocols/openai-compatible-responses.js +1 -0
- package/dist/protocols/openai-responses.d.ts +10 -0
- package/dist/protocols/openai-responses.js +2 -2
- package/dist/protocols/shared.d.ts +1 -1
- package/dist/protocols/shared.js +4 -4
- package/dist/protocols/utils/partial-json.js +57 -2
- package/dist/protocols/utils/tool-stream.d.ts +1 -1
- package/dist/protocols/utils/tool-stream.js +15 -21
- package/dist/protocols/xai-responses.d.ts +10 -0
- package/dist/providers/openrouter.js +1 -3
- package/dist/providers/xai.js +1 -1
- package/dist/route/client.js +3 -1
- package/dist/schema/options.d.ts +1 -0
- package/dist/schema/options.js +1 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +12 -0
- package/package.json +3 -3
package/dist/image-client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { RequestExecutor } from "./route/executor.js";
|
|
3
|
+
import { type AIError } from "./schema/index.js";
|
|
3
4
|
import type { ImageOptions, ImageRequestFor, ImageResponse } from "./image.js";
|
|
4
|
-
import type { AIError } from "./schema/index.js";
|
|
5
5
|
export type Execute = RequestExecutor.Interface["execute"];
|
|
6
6
|
export interface Interface {
|
|
7
7
|
readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError>;
|
package/dist/image-client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
2
|
import { RequestExecutor } from "./route/executor.js";
|
|
3
|
+
import { mergeHttpOptions } from "./schema/index.js";
|
|
4
|
+
import { sanitizeSurrogates } from "./utils/sanitize.js";
|
|
3
5
|
export class Service extends Context.Service()("@opencode/ImageClient") {
|
|
4
6
|
}
|
|
5
7
|
export const generate = (request) => Effect.gen(function* () {
|
|
@@ -9,7 +11,14 @@ export const generate = (request) => Effect.gen(function* () {
|
|
|
9
11
|
export const layer = Layer.effect(Service, Effect.gen(function* () {
|
|
10
12
|
const executor = yield* RequestExecutor.Service;
|
|
11
13
|
return Service.of({
|
|
12
|
-
generate: (request) => request.model.route.generate(
|
|
14
|
+
generate: (request) => request.model.route.generate({
|
|
15
|
+
...sanitizeSurrogates({
|
|
16
|
+
...request,
|
|
17
|
+
model: undefined,
|
|
18
|
+
http: mergeHttpOptions(request.model.http, request.http),
|
|
19
|
+
}),
|
|
20
|
+
model: request.model,
|
|
21
|
+
}, executor.execute),
|
|
13
22
|
});
|
|
14
23
|
}));
|
|
15
24
|
export const ImageClient = {
|
|
@@ -584,15 +584,7 @@ export declare const protocol: Protocol<{
|
|
|
584
584
|
readonly type?: string | undefined;
|
|
585
585
|
readonly message?: string | undefined;
|
|
586
586
|
} | undefined;
|
|
587
|
-
readonly delta?:
|
|
588
|
-
readonly type?: string | undefined;
|
|
589
|
-
readonly text?: string | undefined;
|
|
590
|
-
readonly thinking?: string | undefined;
|
|
591
|
-
readonly signature?: string | undefined;
|
|
592
|
-
readonly partial_json?: string | undefined;
|
|
593
|
-
readonly stop_reason?: string | null | undefined;
|
|
594
|
-
readonly stop_sequence?: string | null | undefined;
|
|
595
|
-
} | undefined;
|
|
587
|
+
readonly delta?: unknown;
|
|
596
588
|
readonly index?: number | undefined;
|
|
597
589
|
readonly usage?: {
|
|
598
590
|
readonly [x: string]: unknown;
|
|
@@ -609,18 +601,7 @@ export declare const protocol: Protocol<{
|
|
|
609
601
|
readonly thinking_tokens?: number | undefined;
|
|
610
602
|
} | null | undefined;
|
|
611
603
|
} | undefined;
|
|
612
|
-
readonly content_block?:
|
|
613
|
-
readonly type: string;
|
|
614
|
-
readonly id?: string | undefined;
|
|
615
|
-
readonly data?: string | undefined;
|
|
616
|
-
readonly name?: string | undefined;
|
|
617
|
-
readonly input?: unknown;
|
|
618
|
-
readonly text?: string | undefined;
|
|
619
|
-
readonly content?: unknown;
|
|
620
|
-
readonly thinking?: string | undefined;
|
|
621
|
-
readonly signature?: string | undefined;
|
|
622
|
-
readonly tool_use_id?: string | undefined;
|
|
623
|
-
} | undefined;
|
|
604
|
+
readonly content_block?: unknown;
|
|
624
605
|
}, {
|
|
625
606
|
tools: Partial<Record<number, ToolStream.PendingTool>>;
|
|
626
607
|
reasoningSignatures: {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
3
|
import { Tool } from "@opencode-ai/schema/tool";
|
|
4
4
|
import { Route } from "../route/client.js";
|
|
5
5
|
import { Auth } from "../route/auth.js";
|
|
@@ -264,6 +264,7 @@ const AnthropicStreamBlock = Schema.Struct({
|
|
|
264
264
|
tool_use_id: Schema.optional(Schema.String),
|
|
265
265
|
content: Schema.optional(Schema.Unknown),
|
|
266
266
|
});
|
|
267
|
+
const decodeAnthropicStreamBlock = Schema.decodeUnknownOption(AnthropicStreamBlock);
|
|
267
268
|
const AnthropicStreamDelta = Schema.Struct({
|
|
268
269
|
type: Schema.optional(Schema.String),
|
|
269
270
|
text: Schema.optional(Schema.String),
|
|
@@ -273,12 +274,13 @@ const AnthropicStreamDelta = Schema.Struct({
|
|
|
273
274
|
stop_reason: optionalNull(Schema.String),
|
|
274
275
|
stop_sequence: optionalNull(Schema.String),
|
|
275
276
|
});
|
|
277
|
+
const decodeAnthropicStreamDelta = Schema.decodeUnknownOption(AnthropicStreamDelta);
|
|
276
278
|
const AnthropicEvent = Schema.Struct({
|
|
277
279
|
type: Schema.String,
|
|
278
280
|
index: Schema.optional(Schema.Number),
|
|
279
281
|
message: Schema.optional(Schema.Struct({ usage: Schema.optional(AnthropicUsage) })),
|
|
280
|
-
content_block: Schema.optional(
|
|
281
|
-
delta: Schema.optional(
|
|
282
|
+
content_block: Schema.optional(Schema.Unknown),
|
|
283
|
+
delta: Schema.optional(Schema.Unknown),
|
|
282
284
|
usage: Schema.optional(AnthropicUsage),
|
|
283
285
|
// `type` and `message` are both required per Anthropic's spec, but
|
|
284
286
|
// OpenAI-compatible proxies and gateway translations occasionally drop one
|
|
@@ -1044,6 +1046,8 @@ const onContentBlockStart = (state, event) => {
|
|
|
1044
1046
|
const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
|
|
1045
1047
|
const delta = event.delta;
|
|
1046
1048
|
if (delta?.type === "text_delta" && delta.text) {
|
|
1049
|
+
if (!state.lifecycle.text.has(`text-${event.index ?? 0}`))
|
|
1050
|
+
return [state, NO_EVENTS];
|
|
1047
1051
|
const events = [];
|
|
1048
1052
|
return [
|
|
1049
1053
|
{ ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.index ?? 0}`, delta.text) },
|
|
@@ -1051,6 +1055,8 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
|
|
|
1051
1055
|
];
|
|
1052
1056
|
}
|
|
1053
1057
|
if (delta?.type === "thinking_delta" && delta.thinking) {
|
|
1058
|
+
if (!state.lifecycle.reasoning.has(`reasoning-${event.index ?? 0}`))
|
|
1059
|
+
return [state, NO_EVENTS];
|
|
1054
1060
|
const events = [];
|
|
1055
1061
|
return [
|
|
1056
1062
|
{
|
|
@@ -1062,6 +1068,8 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
|
|
|
1062
1068
|
}
|
|
1063
1069
|
if (delta?.type === "signature_delta" && delta.signature) {
|
|
1064
1070
|
const index = event.index ?? 0;
|
|
1071
|
+
if (!state.lifecycle.reasoning.has(`reasoning-${index}`))
|
|
1072
|
+
return [state, NO_EVENTS];
|
|
1065
1073
|
return [
|
|
1066
1074
|
{
|
|
1067
1075
|
...state,
|
|
@@ -1148,25 +1156,62 @@ const onError = (event) => Effect.fail(new AIError({
|
|
|
1148
1156
|
method: "stream",
|
|
1149
1157
|
reason: classifyProviderFailure({ message: providerErrorMessage(event), code: event.error?.type }),
|
|
1150
1158
|
}));
|
|
1159
|
+
const isKnownStreamBlockType = (type) => type === "text" ||
|
|
1160
|
+
type === "thinking" ||
|
|
1161
|
+
type === "redacted_thinking" ||
|
|
1162
|
+
type === "tool_use" ||
|
|
1163
|
+
type === "server_tool_use" ||
|
|
1164
|
+
isServerToolResultType(type);
|
|
1165
|
+
const isKnownStreamDeltaType = (type) => type === "text_delta" || type === "thinking_delta" || type === "signature_delta" || type === "input_json_delta";
|
|
1166
|
+
const invalidStreamEvent = (event) => Effect.fail(ProviderShared.eventError(ADAPTER, "Invalid anthropic/anthropic-messages stream event", ProviderShared.encodeJson(event)));
|
|
1151
1167
|
const step = (state, event) => {
|
|
1168
|
+
if (!SSE_EVENTS.has(event.type))
|
|
1169
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1170
|
+
if (event.type !== "content_block_start" &&
|
|
1171
|
+
event.content_block !== undefined &&
|
|
1172
|
+
Option.isNone(decodeAnthropicStreamBlock(event.content_block)))
|
|
1173
|
+
return invalidStreamEvent(event);
|
|
1174
|
+
if (event.type !== "content_block_delta" &&
|
|
1175
|
+
event.delta !== undefined &&
|
|
1176
|
+
Option.isNone(decodeAnthropicStreamDelta(event.delta)))
|
|
1177
|
+
return invalidStreamEvent(event);
|
|
1152
1178
|
if (event.type === "message_start")
|
|
1153
1179
|
return Effect.succeed(onMessageStart(state, event));
|
|
1154
1180
|
if (event.type === "content_block_start") {
|
|
1155
|
-
|
|
1156
|
-
|
|
1181
|
+
if (!ProviderShared.isRecord(event.content_block) || typeof event.content_block.type !== "string")
|
|
1182
|
+
return invalidStreamEvent(event);
|
|
1183
|
+
if (!isKnownStreamBlockType(event.content_block.type))
|
|
1184
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1185
|
+
const decoded = decodeAnthropicStreamBlock(event.content_block);
|
|
1186
|
+
if (Option.isNone(decoded))
|
|
1187
|
+
return invalidStreamEvent(event);
|
|
1188
|
+
const block = decoded.value;
|
|
1189
|
+
if (block.type === "tool_use" || block.type === "server_tool_use") {
|
|
1157
1190
|
if (event.index === undefined)
|
|
1158
1191
|
return Effect.fail(ProviderShared.eventError(ADAPTER, `Anthropic ${block.type} missing index`));
|
|
1159
1192
|
if (!block.id)
|
|
1160
1193
|
return Effect.fail(ProviderShared.eventError(ADAPTER, `Anthropic tool_use missing id at index ${event.index}`));
|
|
1161
1194
|
}
|
|
1162
|
-
return Effect.succeed(onContentBlockStart(state, event));
|
|
1195
|
+
return Effect.succeed(onContentBlockStart(state, { ...event, content_block: block }));
|
|
1196
|
+
}
|
|
1197
|
+
if (event.type === "content_block_delta") {
|
|
1198
|
+
if (!ProviderShared.isRecord(event.delta))
|
|
1199
|
+
return invalidStreamEvent(event);
|
|
1200
|
+
if (typeof event.delta.type === "string" && !isKnownStreamDeltaType(event.delta.type))
|
|
1201
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1202
|
+
const decoded = decodeAnthropicStreamDelta(event.delta);
|
|
1203
|
+
if (Option.isNone(decoded))
|
|
1204
|
+
return invalidStreamEvent(event);
|
|
1205
|
+
return onContentBlockDelta(state, { ...event, delta: decoded.value });
|
|
1163
1206
|
}
|
|
1164
|
-
if (event.type === "content_block_delta")
|
|
1165
|
-
return onContentBlockDelta(state, event);
|
|
1166
1207
|
if (event.type === "content_block_stop")
|
|
1167
1208
|
return onContentBlockStop(state, event);
|
|
1168
|
-
if (event.type === "message_delta")
|
|
1169
|
-
|
|
1209
|
+
if (event.type === "message_delta") {
|
|
1210
|
+
const decoded = decodeAnthropicStreamDelta(event.delta);
|
|
1211
|
+
if (Option.isNone(decoded))
|
|
1212
|
+
return invalidStreamEvent(event);
|
|
1213
|
+
return Effect.succeed(onMessageDelta(state, { ...event, delta: decoded.value }));
|
|
1214
|
+
}
|
|
1170
1215
|
if (event.type === "message_stop")
|
|
1171
1216
|
return onMessageStop(state);
|
|
1172
1217
|
if (event.type === "error")
|
|
@@ -273,6 +273,14 @@ export declare const protocol: Protocol<{
|
|
|
273
273
|
} | undefined;
|
|
274
274
|
readonly metrics?: unknown;
|
|
275
275
|
} | undefined;
|
|
276
|
+
readonly exception?: {
|
|
277
|
+
readonly type: string;
|
|
278
|
+
readonly details: {
|
|
279
|
+
readonly message?: string | undefined;
|
|
280
|
+
readonly originalMessage?: string | undefined;
|
|
281
|
+
readonly originalStatusCode?: number | undefined;
|
|
282
|
+
};
|
|
283
|
+
} | undefined;
|
|
276
284
|
readonly messageStart?: {
|
|
277
285
|
readonly role: string;
|
|
278
286
|
} | undefined;
|
|
@@ -307,31 +315,6 @@ export declare const protocol: Protocol<{
|
|
|
307
315
|
readonly stopReason: string;
|
|
308
316
|
readonly additionalModelResponseFields?: unknown;
|
|
309
317
|
} | undefined;
|
|
310
|
-
readonly internalServerException?: {
|
|
311
|
-
readonly message?: string | undefined;
|
|
312
|
-
readonly originalMessage?: string | undefined;
|
|
313
|
-
readonly originalStatusCode?: number | undefined;
|
|
314
|
-
} | undefined;
|
|
315
|
-
readonly modelStreamErrorException?: {
|
|
316
|
-
readonly message?: string | undefined;
|
|
317
|
-
readonly originalMessage?: string | undefined;
|
|
318
|
-
readonly originalStatusCode?: number | undefined;
|
|
319
|
-
} | undefined;
|
|
320
|
-
readonly validationException?: {
|
|
321
|
-
readonly message?: string | undefined;
|
|
322
|
-
readonly originalMessage?: string | undefined;
|
|
323
|
-
readonly originalStatusCode?: number | undefined;
|
|
324
|
-
} | undefined;
|
|
325
|
-
readonly throttlingException?: {
|
|
326
|
-
readonly message?: string | undefined;
|
|
327
|
-
readonly originalMessage?: string | undefined;
|
|
328
|
-
readonly originalStatusCode?: number | undefined;
|
|
329
|
-
} | undefined;
|
|
330
|
-
readonly serviceUnavailableException?: {
|
|
331
|
-
readonly message?: string | undefined;
|
|
332
|
-
readonly originalMessage?: string | undefined;
|
|
333
|
-
readonly originalStatusCode?: number | undefined;
|
|
334
|
-
} | undefined;
|
|
335
318
|
}, {
|
|
336
319
|
readonly metadata?: {
|
|
337
320
|
readonly usage?: {
|
|
@@ -343,6 +326,14 @@ export declare const protocol: Protocol<{
|
|
|
343
326
|
} | undefined;
|
|
344
327
|
readonly metrics?: unknown;
|
|
345
328
|
} | undefined;
|
|
329
|
+
readonly exception?: {
|
|
330
|
+
readonly type: string;
|
|
331
|
+
readonly details: {
|
|
332
|
+
readonly message?: string | undefined;
|
|
333
|
+
readonly originalMessage?: string | undefined;
|
|
334
|
+
readonly originalStatusCode?: number | undefined;
|
|
335
|
+
};
|
|
336
|
+
} | undefined;
|
|
346
337
|
readonly messageStart?: {
|
|
347
338
|
readonly role: string;
|
|
348
339
|
} | undefined;
|
|
@@ -377,31 +368,6 @@ export declare const protocol: Protocol<{
|
|
|
377
368
|
readonly stopReason: string;
|
|
378
369
|
readonly additionalModelResponseFields?: unknown;
|
|
379
370
|
} | undefined;
|
|
380
|
-
readonly internalServerException?: {
|
|
381
|
-
readonly message?: string | undefined;
|
|
382
|
-
readonly originalMessage?: string | undefined;
|
|
383
|
-
readonly originalStatusCode?: number | undefined;
|
|
384
|
-
} | undefined;
|
|
385
|
-
readonly modelStreamErrorException?: {
|
|
386
|
-
readonly message?: string | undefined;
|
|
387
|
-
readonly originalMessage?: string | undefined;
|
|
388
|
-
readonly originalStatusCode?: number | undefined;
|
|
389
|
-
} | undefined;
|
|
390
|
-
readonly validationException?: {
|
|
391
|
-
readonly message?: string | undefined;
|
|
392
|
-
readonly originalMessage?: string | undefined;
|
|
393
|
-
readonly originalStatusCode?: number | undefined;
|
|
394
|
-
} | undefined;
|
|
395
|
-
readonly throttlingException?: {
|
|
396
|
-
readonly message?: string | undefined;
|
|
397
|
-
readonly originalMessage?: string | undefined;
|
|
398
|
-
readonly originalStatusCode?: number | undefined;
|
|
399
|
-
} | undefined;
|
|
400
|
-
readonly serviceUnavailableException?: {
|
|
401
|
-
readonly message?: string | undefined;
|
|
402
|
-
readonly originalMessage?: string | undefined;
|
|
403
|
-
readonly originalStatusCode?: number | undefined;
|
|
404
|
-
} | undefined;
|
|
405
371
|
}, ParserState>;
|
|
406
372
|
export declare const route: Route<{
|
|
407
373
|
readonly messages: readonly ({
|
|
@@ -149,11 +149,7 @@ const BedrockEvent = Schema.Struct({
|
|
|
149
149
|
usage: Schema.optional(BedrockUsageSchema),
|
|
150
150
|
metrics: Schema.optional(Schema.Unknown),
|
|
151
151
|
})),
|
|
152
|
-
|
|
153
|
-
modelStreamErrorException: Schema.optional(BedrockStreamException),
|
|
154
|
-
validationException: Schema.optional(BedrockStreamException),
|
|
155
|
-
throttlingException: Schema.optional(BedrockStreamException),
|
|
156
|
-
serviceUnavailableException: Schema.optional(BedrockStreamException),
|
|
152
|
+
exception: Schema.optional(Schema.Struct({ type: Schema.String, details: BedrockStreamException })),
|
|
157
153
|
});
|
|
158
154
|
// =============================================================================
|
|
159
155
|
// Request Lowering
|
|
@@ -507,20 +503,13 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
507
503
|
[],
|
|
508
504
|
];
|
|
509
505
|
}
|
|
510
|
-
|
|
511
|
-
["internalServerException", event.internalServerException],
|
|
512
|
-
["modelStreamErrorException", event.modelStreamErrorException],
|
|
513
|
-
["serviceUnavailableException", event.serviceUnavailableException],
|
|
514
|
-
["throttlingException", event.throttlingException],
|
|
515
|
-
["validationException", event.validationException],
|
|
516
|
-
].find((entry) => entry[1] !== undefined);
|
|
517
|
-
if (exception) {
|
|
506
|
+
if (event.exception) {
|
|
518
507
|
return yield* new AIError({
|
|
519
508
|
module: ADAPTER,
|
|
520
509
|
method: "stream",
|
|
521
510
|
reason: classifyProviderFailure({
|
|
522
|
-
message: exception
|
|
523
|
-
code: exception
|
|
511
|
+
message: event.exception.details.message ?? event.exception.details.originalMessage ?? "Bedrock Converse stream error",
|
|
512
|
+
code: event.exception.type,
|
|
524
513
|
}),
|
|
525
514
|
});
|
|
526
515
|
}
|
|
@@ -56,7 +56,7 @@ const consumeFrames = (route) => (state, chunk) => Effect.gen(function* () {
|
|
|
56
56
|
// against ad-hoc `JSON.parse` calls.
|
|
57
57
|
const parsed = (yield* ProviderShared.parseJson(route, payload, "Failed to parse Bedrock Converse event-stream payload"));
|
|
58
58
|
delete parsed.p;
|
|
59
|
-
out.push({ [eventType]: parsed });
|
|
59
|
+
out.push(messageType === "exception" ? { exception: { type: eventType, details: parsed } } : { [eventType]: parsed });
|
|
60
60
|
}
|
|
61
61
|
return [cursor, out];
|
|
62
62
|
});
|
|
@@ -133,7 +133,12 @@ export const driver = (input) => {
|
|
|
133
133
|
...observation,
|
|
134
134
|
checkpoint: {
|
|
135
135
|
protocol: PROTOCOL,
|
|
136
|
-
value: {
|
|
136
|
+
value: {
|
|
137
|
+
version: VERSION,
|
|
138
|
+
responseID,
|
|
139
|
+
request,
|
|
140
|
+
output: event.response?.output ? [...event.response.output] : output.slice(),
|
|
141
|
+
},
|
|
137
142
|
},
|
|
138
143
|
};
|
|
139
144
|
}),
|
|
@@ -387,6 +387,7 @@ export declare const Event: Schema.StructWithRest<Schema.Struct<{
|
|
|
387
387
|
readonly arguments: Schema.optional<Schema.String>;
|
|
388
388
|
readonly text: Schema.optional<Schema.String>;
|
|
389
389
|
readonly item_id: Schema.optional<Schema.String>;
|
|
390
|
+
readonly output_index: Schema.optional<Schema.Number>;
|
|
390
391
|
readonly summary_index: Schema.optional<Schema.Number>;
|
|
391
392
|
readonly item: Schema.optional<Schema.StructWithRest<Schema.Struct<{
|
|
392
393
|
readonly type: Schema.String;
|
|
@@ -402,6 +403,14 @@ export declare const Event: Schema.StructWithRest<Schema.Struct<{
|
|
|
402
403
|
readonly incomplete_details: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
403
404
|
readonly reason: Schema.optional<Schema.String>;
|
|
404
405
|
}>>>;
|
|
406
|
+
readonly output: Schema.optional<Schema.$Array<Schema.StructWithRest<Schema.Struct<{
|
|
407
|
+
readonly type: Schema.String;
|
|
408
|
+
readonly id: Schema.optional<Schema.String>;
|
|
409
|
+
readonly call_id: Schema.optional<Schema.String>;
|
|
410
|
+
readonly name: Schema.optional<Schema.String>;
|
|
411
|
+
readonly arguments: Schema.optional<Schema.String>;
|
|
412
|
+
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
413
|
+
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
|
|
405
414
|
readonly usage: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
406
415
|
readonly input_tokens: Schema.optional<Schema.Number>;
|
|
407
416
|
readonly input_tokens_details: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
@@ -453,6 +462,7 @@ export interface ParserState {
|
|
|
453
462
|
readonly tools: ToolStream.State<string>;
|
|
454
463
|
readonly hasFunctionCall: boolean;
|
|
455
464
|
readonly lifecycle: Lifecycle.State;
|
|
465
|
+
readonly outputItems: Readonly<Record<number, string>>;
|
|
456
466
|
readonly messageItems: ReadonlySet<string>;
|
|
457
467
|
readonly messagePhases: Readonly<Record<string, MessagePhase | null>>;
|
|
458
468
|
readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>;
|
|
@@ -653,10 +663,11 @@ export declare const fromRequest: (request: LLMRequest) => Effect.Effect<{
|
|
|
653
663
|
export declare const providerMetadata: (state: ParserState, metadata: Record<string, unknown>) => ProviderMetadata;
|
|
654
664
|
export type StepResult = readonly [ParserState, ReadonlyArray<LLMEvent>];
|
|
655
665
|
export declare const terminal: (event: Event) => boolean;
|
|
666
|
+
export declare const outputItemID: (state: ParserState, event: Event) => string | undefined;
|
|
656
667
|
export declare const onReasoningDelta: (state: ParserState, event: Event, itemID: string) => StepResult;
|
|
657
668
|
export declare const onReasoningDone: (state: ParserState, event: Event, itemID: string) => StepResult;
|
|
658
669
|
export declare const providerFailure: (id: string, event: Event, fallback: string) => AIError;
|
|
659
|
-
export declare const step: (state: ParserState,
|
|
670
|
+
export declare const step: (state: ParserState, input: Event) => AIError | Effect.Effect<[ParserState, readonly ({
|
|
660
671
|
readonly type: "step-start";
|
|
661
672
|
readonly index: number;
|
|
662
673
|
} | {
|
|
@@ -838,7 +849,7 @@ export declare const step: (state: ParserState, event: Event) => AIError | Effec
|
|
|
838
849
|
};
|
|
839
850
|
} | undefined;
|
|
840
851
|
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
|
|
841
|
-
})[]], AIError, never>;
|
|
852
|
+
})[]], AIError, never> | Effect.Effect<StepResult, never, never>;
|
|
842
853
|
/**
|
|
843
854
|
* The provider-neutral Open Responses protocol. Provider-specific Responses
|
|
844
855
|
* implementations compose this baseline with their own tools and event variants.
|
|
@@ -987,6 +998,15 @@ export declare const protocol: Protocol<{
|
|
|
987
998
|
readonly response?: {
|
|
988
999
|
readonly [x: string]: unknown;
|
|
989
1000
|
readonly id?: string | undefined;
|
|
1001
|
+
readonly output?: readonly {
|
|
1002
|
+
readonly [x: string]: unknown;
|
|
1003
|
+
readonly type: string;
|
|
1004
|
+
readonly id?: string | undefined;
|
|
1005
|
+
readonly name?: string | undefined;
|
|
1006
|
+
readonly arguments?: string | undefined;
|
|
1007
|
+
readonly encrypted_content?: string | null | undefined;
|
|
1008
|
+
readonly call_id?: string | undefined;
|
|
1009
|
+
}[] | undefined;
|
|
990
1010
|
readonly error?: {
|
|
991
1011
|
readonly type?: string | null | undefined;
|
|
992
1012
|
readonly code?: string | null | undefined;
|
|
@@ -1014,6 +1034,7 @@ export declare const protocol: Protocol<{
|
|
|
1014
1034
|
readonly param?: string | null | undefined;
|
|
1015
1035
|
readonly status_code?: unknown;
|
|
1016
1036
|
readonly item_id?: string | undefined;
|
|
1037
|
+
readonly output_index?: number | undefined;
|
|
1017
1038
|
readonly summary_index?: number | undefined;
|
|
1018
1039
|
}, ParserState>;
|
|
1019
1040
|
export declare const httpTransport: HttpTransport.HttpJsonTransport<{
|
|
@@ -207,12 +207,14 @@ export const Event = Schema.StructWithRest(Schema.Struct({
|
|
|
207
207
|
arguments: Schema.optional(Schema.String),
|
|
208
208
|
text: Schema.optional(Schema.String),
|
|
209
209
|
item_id: Schema.optional(Schema.String),
|
|
210
|
+
output_index: Schema.optional(Schema.Number),
|
|
210
211
|
summary_index: Schema.optional(Schema.Number),
|
|
211
212
|
item: Schema.optional(StreamItem),
|
|
212
213
|
response: Schema.optional(Schema.StructWithRest(Schema.Struct({
|
|
213
214
|
id: Schema.optional(Schema.String),
|
|
214
215
|
service_tier: optionalNull(Schema.String),
|
|
215
216
|
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.optional(Schema.String) })),
|
|
217
|
+
output: Schema.optional(Schema.Array(StreamItem)),
|
|
216
218
|
usage: optionalNull(OpenResponsesUsage),
|
|
217
219
|
error: optionalNull(OpenResponsesErrorPayload),
|
|
218
220
|
}), [Schema.Record(Schema.String, Schema.Unknown)])),
|
|
@@ -462,16 +464,11 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
|
|
|
462
464
|
});
|
|
463
465
|
}
|
|
464
466
|
}
|
|
465
|
-
|
|
466
|
-
// complete item has encrypted state. Summary blocks for one item may carry
|
|
467
|
-
// that state only on the last block, so filter after they have been joined.
|
|
468
|
-
return store === false
|
|
469
|
-
? input.filter((item) => !("type" in item) || item.type !== "reasoning" || typeof item.encrypted_content === "string")
|
|
470
|
-
: input;
|
|
467
|
+
return input;
|
|
471
468
|
});
|
|
472
469
|
const lowerOptions = (request) => {
|
|
473
470
|
const options = OpenResponsesOptions.resolve(request);
|
|
474
|
-
const cacheKey = ProviderShared.
|
|
471
|
+
const cacheKey = ProviderShared.promptCacheKey(request);
|
|
475
472
|
const parallelToolCalls = resolveParallelToolCalls(request);
|
|
476
473
|
return {
|
|
477
474
|
...(options.instructions ? { instructions: options.instructions } : {}),
|
|
@@ -603,6 +600,7 @@ const onOutputTextDone = (state, event, id) => {
|
|
|
603
600
|
const events = [];
|
|
604
601
|
return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, id) }, events];
|
|
605
602
|
};
|
|
603
|
+
export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
|
|
606
604
|
export const onReasoningDelta = (state, event, itemID) => {
|
|
607
605
|
const item = state.reasoningItems[itemID];
|
|
608
606
|
if (!event.delta || !item)
|
|
@@ -855,27 +853,37 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
|
|
|
855
853
|
return [state, NO_EVENTS];
|
|
856
854
|
});
|
|
857
855
|
const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state, event) {
|
|
856
|
+
const reconciled = event.type === "response.completed"
|
|
857
|
+
? yield* Effect.reduce(event.response?.output ?? [], () => [state, NO_EVENTS], ([current, events], item) => {
|
|
858
|
+
if (!item.id ||
|
|
859
|
+
((item.type !== "function_call" || !current.tools[item.id]) &&
|
|
860
|
+
(item.type !== "reasoning" || !current.reasoningItems[item.id])))
|
|
861
|
+
return Effect.succeed([current, events]);
|
|
862
|
+
return onOutputItemDone(current, { type: "response.output_item.done", item }).pipe(Effect.map(([next, emitted]) => [next, [...events, ...emitted]]));
|
|
863
|
+
})
|
|
864
|
+
: [state, NO_EVENTS];
|
|
865
|
+
const current = reconciled[0];
|
|
858
866
|
// Some compatible providers omit output_item.done even after completing the response.
|
|
859
867
|
const pending = event.type === "response.completed"
|
|
860
|
-
? yield* ToolStream.finishAll(
|
|
861
|
-
: { tools:
|
|
862
|
-
const events = [...pending.events];
|
|
868
|
+
? yield* ToolStream.finishAll(current.id, current.tools)
|
|
869
|
+
: { tools: current.tools, events: NO_EVENTS };
|
|
870
|
+
const events = [...reconciled[1], ...pending.events];
|
|
863
871
|
const hasFunctionCall = pending.events.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
|
|
864
|
-
|
|
865
|
-
const lifecycle = Lifecycle.finish(
|
|
872
|
+
current.hasFunctionCall;
|
|
873
|
+
const lifecycle = Lifecycle.finish(current.lifecycle, events, {
|
|
866
874
|
reason: {
|
|
867
875
|
normalized: mapFinishReason(event, hasFunctionCall),
|
|
868
876
|
raw: event.response?.incomplete_details?.reason,
|
|
869
877
|
},
|
|
870
|
-
usage: mapUsage(event.response?.usage,
|
|
878
|
+
usage: mapUsage(event.response?.usage, current.providerMetadataKey),
|
|
871
879
|
providerMetadata: event.response?.id || event.response?.service_tier
|
|
872
|
-
? providerMetadata(
|
|
880
|
+
? providerMetadata(current, {
|
|
873
881
|
responseId: event.response.id,
|
|
874
882
|
serviceTier: event.response.service_tier,
|
|
875
883
|
})
|
|
876
884
|
: undefined,
|
|
877
885
|
});
|
|
878
|
-
return [{ ...
|
|
886
|
+
return [{ ...current, lifecycle, hasFunctionCall, tools: pending.tools }, events];
|
|
879
887
|
});
|
|
880
888
|
// Build the prettiest summary available from whatever the provider supplied.
|
|
881
889
|
// When both code and message are present, prefix the code so consumers see
|
|
@@ -917,7 +925,10 @@ export const providerFailure = (id, event, fallback) => {
|
|
|
917
925
|
});
|
|
918
926
|
};
|
|
919
927
|
const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
|
|
920
|
-
export const step = (state,
|
|
928
|
+
export const step = (state, input) => {
|
|
929
|
+
const event = input.item_id && outputItemID(state, input) !== input.item_id
|
|
930
|
+
? { ...input, item_id: outputItemID(state, input) }
|
|
931
|
+
: input;
|
|
921
932
|
if (event.type === "response.output_text.delta" || event.type === "response.output_text.done") {
|
|
922
933
|
if (!event.item_id)
|
|
923
934
|
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
@@ -956,7 +967,9 @@ export const step = (state, event) => {
|
|
|
956
967
|
if (event.type === "response.output_item.added") {
|
|
957
968
|
if (event.item?.type === "message" && !event.item.id)
|
|
958
969
|
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
|
|
959
|
-
return Effect.succeed(onOutputItemAdded(
|
|
970
|
+
return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && event.item?.id
|
|
971
|
+
? { ...state, outputItems: { ...state.outputItems, [event.output_index]: event.item.id } }
|
|
972
|
+
: state, event));
|
|
960
973
|
}
|
|
961
974
|
if (event.type === "response.function_call_arguments.delta" || event.type === "response.function_call_arguments.done")
|
|
962
975
|
return event.item_id
|
|
@@ -989,6 +1002,7 @@ export const initial = (request, extension = BASE) => ({
|
|
|
989
1002
|
hasFunctionCall: false,
|
|
990
1003
|
tools: ToolStream.empty(),
|
|
991
1004
|
lifecycle: Lifecycle.initial(),
|
|
1005
|
+
outputItems: {},
|
|
992
1006
|
messageItems: new Set(),
|
|
993
1007
|
messagePhases: {},
|
|
994
1008
|
reasoningItems: {},
|
|
@@ -285,6 +285,7 @@ export interface ParserState {
|
|
|
285
285
|
}
|
|
286
286
|
interface LoweringOptions {
|
|
287
287
|
readonly cacheControl?: (cache: CacheHint | undefined) => Schema.Schema.Type<typeof OpenAIChatCacheControl> | undefined;
|
|
288
|
+
readonly toolCallID?: (id: string) => string;
|
|
288
289
|
}
|
|
289
290
|
export declare const fromRequest: (request: LLMRequest, options?: LoweringOptions | undefined) => Effect.Effect<{
|
|
290
291
|
reasoning_effort?: import("./utils/open-responses-options.js").ReasoningEffort | undefined;
|
|
@@ -192,8 +192,8 @@ const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("OpenAI C
|
|
|
192
192
|
required: () => "required",
|
|
193
193
|
tool: (name) => ({ type: "function", function: { name } }),
|
|
194
194
|
});
|
|
195
|
-
const lowerToolCall = (part) => ({
|
|
196
|
-
id: part.id,
|
|
195
|
+
const lowerToolCall = (part, options) => ({
|
|
196
|
+
id: options.toolCallID?.(part.id) ?? part.id,
|
|
197
197
|
type: "function",
|
|
198
198
|
function: {
|
|
199
199
|
name: part.name,
|
|
@@ -257,7 +257,7 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
|
|
257
257
|
continue;
|
|
258
258
|
}
|
|
259
259
|
if (part.type === "tool-call") {
|
|
260
|
-
toolCalls.push(lowerToolCall(part));
|
|
260
|
+
toolCalls.push(lowerToolCall(part, options));
|
|
261
261
|
continue;
|
|
262
262
|
}
|
|
263
263
|
}
|
|
@@ -307,7 +307,7 @@ const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (m
|
|
|
307
307
|
if (part.result.type !== "content") {
|
|
308
308
|
messages.push({
|
|
309
309
|
role: "tool",
|
|
310
|
-
tool_call_id: part.id,
|
|
310
|
+
tool_call_id: options.toolCallID?.(part.id) ?? part.id,
|
|
311
311
|
content: ProviderShared.toolResultText(part),
|
|
312
312
|
cache_control: options.cacheControl?.(part.cache),
|
|
313
313
|
});
|
|
@@ -317,7 +317,7 @@ const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (m
|
|
|
317
317
|
const text = content.filter((item) => item.type === "text").map((item) => item.text);
|
|
318
318
|
messages.push({
|
|
319
319
|
role: "tool",
|
|
320
|
-
tool_call_id: part.id,
|
|
320
|
+
tool_call_id: options.toolCallID?.(part.id) ?? part.id,
|
|
321
321
|
content: text.join("\n"),
|
|
322
322
|
cache_control: options.cacheControl?.(part.cache),
|
|
323
323
|
});
|
|
@@ -349,13 +349,35 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
|
|
|
349
349
|
]
|
|
350
350
|
: [{ role: "system", content: ProviderShared.joinText(request.system) }];
|
|
351
351
|
const messages = [...system];
|
|
352
|
+
const modelID = request.model.id.toLowerCase();
|
|
353
|
+
const mistral = ["mistral", "devstral", "codestral", "pixtral", "mixtral"].some((family) => modelID.includes(family));
|
|
354
|
+
const lowering = {
|
|
355
|
+
...options,
|
|
356
|
+
toolCallID: (id) => {
|
|
357
|
+
if (mistral)
|
|
358
|
+
return id.replace(/[^a-zA-Z0-9]/g, "").slice(0, 9).padEnd(9, "0");
|
|
359
|
+
if (modelID.includes("claude"))
|
|
360
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
361
|
+
if (request.model.provider === "openai" || request.model.provider === "azure" || modelID.startsWith("openai/"))
|
|
362
|
+
return id.slice(0, 40);
|
|
363
|
+
return id;
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
const requireAssistantAfterTool = request.model.compatibility?.requireAssistantAfterTool ?? mistral;
|
|
367
|
+
const bridgeTools = () => {
|
|
368
|
+
if (requireAssistantAfterTool && messages.at(-1)?.role === "tool")
|
|
369
|
+
messages.push({ role: "assistant", content: "Done." });
|
|
370
|
+
};
|
|
352
371
|
const pendingImages = [];
|
|
353
372
|
const flushImages = () => {
|
|
354
373
|
if (pendingImages.length === 0)
|
|
355
374
|
return;
|
|
375
|
+
bridgeTools();
|
|
356
376
|
messages.push({ role: "user", content: pendingImages.splice(0) });
|
|
357
377
|
};
|
|
358
378
|
for (const message of request.messages) {
|
|
379
|
+
if (message.role === "user")
|
|
380
|
+
bridgeTools();
|
|
359
381
|
if (message.role === "system") {
|
|
360
382
|
const part = yield* ProviderShared.wrappedSystemUpdate("OpenAI Chat", message);
|
|
361
383
|
if (pendingImages.length > 0) {
|
|
@@ -396,14 +418,16 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
|
|
|
396
418
|
: { role: "user", content: part.text });
|
|
397
419
|
continue;
|
|
398
420
|
}
|
|
421
|
+
if (message.role === "assistant" && message.content.every((part) => part.type === "text" && part.text.trim() === ""))
|
|
422
|
+
continue;
|
|
399
423
|
if (message.role === "tool") {
|
|
400
|
-
const lowered = yield* lowerToolMessages(message,
|
|
424
|
+
const lowered = yield* lowerToolMessages(message, lowering);
|
|
401
425
|
messages.push(...lowered.messages);
|
|
402
426
|
pendingImages.push(...lowered.images);
|
|
403
427
|
continue;
|
|
404
428
|
}
|
|
405
429
|
flushImages();
|
|
406
|
-
messages.push(...(yield* lowerMessage(message, request.model.compatibility?.reasoningField,
|
|
430
|
+
messages.push(...(yield* lowerMessage(message, request.model.compatibility?.reasoningField, lowering)));
|
|
407
431
|
}
|
|
408
432
|
flushImages();
|
|
409
433
|
return messages;
|
|
@@ -517,7 +541,7 @@ const detectZaiToolStream = (provider, baseURL, modelID) => {
|
|
|
517
541
|
};
|
|
518
542
|
const lowerOptions = (request, supportsStore) => {
|
|
519
543
|
const options = OpenAIOptions.resolve(request);
|
|
520
|
-
const cacheKey = ProviderShared.
|
|
544
|
+
const cacheKey = ProviderShared.promptCacheKey(request);
|
|
521
545
|
return {
|
|
522
546
|
...(supportsStore && options.store !== undefined ? { store: options.store } : {}),
|
|
523
547
|
// For providers that support `store`, ensure stateless `store:false` is sent
|
|
@@ -13,5 +13,6 @@ export const route = Route.make({
|
|
|
13
13
|
protocol: OpenResponses.protocol,
|
|
14
14
|
endpoint: Endpoint.path(OpenResponses.PATH),
|
|
15
15
|
transport: OpenResponses.httpTransport,
|
|
16
|
+
defaults: { providerOptions: { store: false, include: ["reasoning.encrypted_content"] } },
|
|
16
17
|
});
|
|
17
18
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
|
|
@@ -286,6 +286,15 @@ export declare const protocol: Protocol<{
|
|
|
286
286
|
readonly response?: {
|
|
287
287
|
readonly [x: string]: unknown;
|
|
288
288
|
readonly id?: string | undefined;
|
|
289
|
+
readonly output?: readonly {
|
|
290
|
+
readonly [x: string]: unknown;
|
|
291
|
+
readonly type: string;
|
|
292
|
+
readonly id?: string | undefined;
|
|
293
|
+
readonly name?: string | undefined;
|
|
294
|
+
readonly arguments?: string | undefined;
|
|
295
|
+
readonly encrypted_content?: string | null | undefined;
|
|
296
|
+
readonly call_id?: string | undefined;
|
|
297
|
+
}[] | undefined;
|
|
289
298
|
readonly error?: {
|
|
290
299
|
readonly type?: string | null | undefined;
|
|
291
300
|
readonly code?: string | null | undefined;
|
|
@@ -313,6 +322,7 @@ export declare const protocol: Protocol<{
|
|
|
313
322
|
readonly param?: string | null | undefined;
|
|
314
323
|
readonly status_code?: unknown;
|
|
315
324
|
readonly item_id?: string | undefined;
|
|
325
|
+
readonly output_index?: number | undefined;
|
|
316
326
|
readonly summary_index?: number | undefined;
|
|
317
327
|
}, OpenResponses.ParserState>;
|
|
318
328
|
export declare const httpTransport: HttpTransport.HttpJsonTransport<{
|
|
@@ -139,7 +139,7 @@ const HOSTED_TOOLS = {
|
|
|
139
139
|
const step = (state, event) => {
|
|
140
140
|
if (event.type === "response.reasoning_text.delta")
|
|
141
141
|
return event.item_id
|
|
142
|
-
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, event.item_id))
|
|
142
|
+
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, OpenResponses.outputItemID(state, event) ?? event.item_id))
|
|
143
143
|
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`);
|
|
144
144
|
if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))
|
|
145
145
|
return ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS);
|
|
@@ -176,6 +176,6 @@ export const route = Route.make({
|
|
|
176
176
|
endpoint,
|
|
177
177
|
auth,
|
|
178
178
|
transport,
|
|
179
|
-
defaults: { providerOptions: { store: false } },
|
|
179
|
+
defaults: { providerOptions: { store: false, include: ["reasoning.encrypted_content"] } },
|
|
180
180
|
});
|
|
181
181
|
export * as OpenAIResponses from "./openai-responses.js";
|
|
@@ -11,7 +11,7 @@ export declare const JsonObject: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
|
11
11
|
export declare const optionalArray: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.$Array<S>>;
|
|
12
12
|
export declare const optionalNull: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.NullOr<S>>;
|
|
13
13
|
export declare const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
14
|
-
export declare const
|
|
14
|
+
export declare const promptCacheKey: (request: LLMRequest) => string | undefined;
|
|
15
15
|
/**
|
|
16
16
|
* Streaming tool-call accumulator. Adapters that build a tool call across
|
|
17
17
|
* multiple `tool-input-delta` chunks store the partial JSON input string here
|
package/dist/protocols/shared.js
CHANGED
|
@@ -16,12 +16,12 @@ export const optionalNull = (schema) => Schema.optional(Schema.NullOr(schema));
|
|
|
16
16
|
export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
17
17
|
// OpenAI limits `prompt_cache_key` to 64 chars; DeepSeek and Zai inherit the same
|
|
18
18
|
// limit via their OpenAI-compatible APIs. Clamp with unicode-aware slicing.
|
|
19
|
-
export const
|
|
20
|
-
if (
|
|
19
|
+
export const promptCacheKey = (request) => {
|
|
20
|
+
if (request.cache === "none" || request.promptCacheKey === undefined)
|
|
21
21
|
return undefined;
|
|
22
|
-
const chars = Array.from(
|
|
22
|
+
const chars = Array.from(request.promptCacheKey);
|
|
23
23
|
if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH)
|
|
24
|
-
return
|
|
24
|
+
return request.promptCacheKey;
|
|
25
25
|
return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
@@ -43,8 +43,58 @@ export function parseJSON(jsonString, allowPartial = Allow.ALL) {
|
|
|
43
43
|
return decodeJson(input);
|
|
44
44
|
}
|
|
45
45
|
catch { }
|
|
46
|
-
|
|
46
|
+
const repaired = repairJSON(input);
|
|
47
|
+
if (repaired !== input) {
|
|
48
|
+
try {
|
|
49
|
+
return decodeJson(repaired);
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return _parseJSON(input, allowPartial);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (repaired !== input)
|
|
58
|
+
return _parseJSON(repaired, allowPartial);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
47
61
|
}
|
|
62
|
+
const repairJSON = (input) => {
|
|
63
|
+
let repaired = "";
|
|
64
|
+
let quoted = false;
|
|
65
|
+
for (let index = 0; index < input.length; index++) {
|
|
66
|
+
const character = input[index];
|
|
67
|
+
if (!quoted) {
|
|
68
|
+
repaired += character;
|
|
69
|
+
if (character === '"')
|
|
70
|
+
quoted = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (character === '"') {
|
|
74
|
+
repaired += character;
|
|
75
|
+
quoted = false;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (character === "\\") {
|
|
79
|
+
const next = input[index + 1];
|
|
80
|
+
if (next === "u" && /^[0-9a-fA-F]{4}$/.test(input.slice(index + 2, index + 6))) {
|
|
81
|
+
repaired += input.slice(index, index + 6);
|
|
82
|
+
index += 5;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (next !== undefined && '"\\/bfnrtu'.includes(next)) {
|
|
86
|
+
repaired += `\\${next}`;
|
|
87
|
+
index++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
repaired += "\\\\";
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const code = character.charCodeAt(0);
|
|
94
|
+
repaired += code <= 0x1f ? `\\u${code.toString(16).padStart(4, "0")}` : character;
|
|
95
|
+
}
|
|
96
|
+
return repaired;
|
|
97
|
+
};
|
|
48
98
|
const _parseJSON = (jsonString, allow) => {
|
|
49
99
|
const length = jsonString.length;
|
|
50
100
|
let index = 0;
|
|
@@ -138,7 +188,12 @@ const _parseJSON = (jsonString, allow) => {
|
|
|
138
188
|
skipBlank();
|
|
139
189
|
index++;
|
|
140
190
|
try {
|
|
141
|
-
object
|
|
191
|
+
Object.defineProperty(object, key, {
|
|
192
|
+
value: parseAny(),
|
|
193
|
+
enumerable: true,
|
|
194
|
+
configurable: true,
|
|
195
|
+
writable: true,
|
|
196
|
+
});
|
|
142
197
|
}
|
|
143
198
|
catch (error) {
|
|
144
199
|
if (Allow.OBJ & allow)
|
|
@@ -61,7 +61,7 @@ export declare const appendOrStart: <K extends StreamKey>(route: string, tools:
|
|
|
61
61
|
export declare const appendExisting: <K extends StreamKey>(route: string, tools: State<K>, key: K, text: string, missingToolMessage: string) => AppendOutcome<K> | AIError;
|
|
62
62
|
/**
|
|
63
63
|
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
|
64
|
-
* from state, and
|
|
64
|
+
* from state, and recover incomplete local arguments when needed.
|
|
65
65
|
* Missing keys are a no-op because some providers emit stop events for
|
|
66
66
|
* non-tool content blocks.
|
|
67
67
|
*/
|
|
@@ -19,34 +19,28 @@ const inputStart = (tool) => LLMEvent.toolInputStart({
|
|
|
19
19
|
providerExecuted: tool.providerExecuted ? true : undefined,
|
|
20
20
|
providerMetadata: tool.providerMetadata,
|
|
21
21
|
});
|
|
22
|
-
const inputDelta = (tool, text) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
...(Option.isSome(input) ? { input: input.value } : {}),
|
|
29
|
-
});
|
|
30
|
-
};
|
|
22
|
+
const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
|
|
23
|
+
id: tool.id,
|
|
24
|
+
name: tool.name,
|
|
25
|
+
text,
|
|
26
|
+
input: Option.getOrElse(parsePartialInput(tool.input), () => ({})),
|
|
27
|
+
});
|
|
31
28
|
const toolCall = (route, tool, inputOverride) => {
|
|
32
29
|
const raw = inputOverride ?? tool.input;
|
|
33
|
-
return parseToolInput(route, tool.name, raw).pipe(Effect.
|
|
30
|
+
return parseToolInput(route, tool.name, raw).pipe(Effect.catch((error) => tool.providerExecuted
|
|
31
|
+
? Effect.fail(error)
|
|
32
|
+
: Effect.succeed(Option.getOrElse(Option.map(parsePartialInput(raw), (input) => input ?? {}), () => ({})))), Effect.map((input) => LLMEvent.toolCall({
|
|
34
33
|
id: tool.id,
|
|
35
34
|
name: tool.name,
|
|
36
35
|
input,
|
|
37
36
|
providerExecuted: tool.providerExecuted ? true : undefined,
|
|
38
37
|
providerMetadata: tool.providerMetadata,
|
|
39
|
-
}))
|
|
40
|
-
? Effect.fail(error)
|
|
41
|
-
: Effect.succeed(LLMEvent.toolInputError({
|
|
42
|
-
id: tool.id,
|
|
43
|
-
name: tool.name,
|
|
44
|
-
raw,
|
|
45
|
-
}))));
|
|
38
|
+
})));
|
|
46
39
|
};
|
|
47
|
-
const finishEvents = (tool, event) =>
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
const finishEvents = (tool, event) => [
|
|
41
|
+
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
42
|
+
event,
|
|
43
|
+
];
|
|
50
44
|
/** Store the updated tool and produce the optional public delta event. */
|
|
51
45
|
const appendTool = (tools, key, tool, text) => {
|
|
52
46
|
const events = [];
|
|
@@ -105,7 +99,7 @@ export const appendExisting = (route, tools, key, text, missingToolMessage) => {
|
|
|
105
99
|
};
|
|
106
100
|
/**
|
|
107
101
|
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
|
108
|
-
* from state, and
|
|
102
|
+
* from state, and recover incomplete local arguments when needed.
|
|
109
103
|
* Missing keys are a no-op because some providers emit stop events for
|
|
110
104
|
* non-tool content blocks.
|
|
111
105
|
*/
|
|
@@ -143,6 +143,15 @@ export declare const protocol: Protocol<{
|
|
|
143
143
|
readonly response?: {
|
|
144
144
|
readonly [x: string]: unknown;
|
|
145
145
|
readonly id?: string | undefined;
|
|
146
|
+
readonly output?: readonly {
|
|
147
|
+
readonly [x: string]: unknown;
|
|
148
|
+
readonly type: string;
|
|
149
|
+
readonly id?: string | undefined;
|
|
150
|
+
readonly name?: string | undefined;
|
|
151
|
+
readonly arguments?: string | undefined;
|
|
152
|
+
readonly encrypted_content?: string | null | undefined;
|
|
153
|
+
readonly call_id?: string | undefined;
|
|
154
|
+
}[] | undefined;
|
|
146
155
|
readonly error?: {
|
|
147
156
|
readonly type?: string | null | undefined;
|
|
148
157
|
readonly code?: string | null | undefined;
|
|
@@ -170,6 +179,7 @@ export declare const protocol: Protocol<{
|
|
|
170
179
|
readonly param?: string | null | undefined;
|
|
171
180
|
readonly status_code?: unknown;
|
|
172
181
|
readonly item_id?: string | undefined;
|
|
182
|
+
readonly output_index?: number | undefined;
|
|
173
183
|
readonly summary_index?: number | undefined;
|
|
174
184
|
}, OpenResponses.ParserState>;
|
|
175
185
|
export * as XAIResponses from "./xai-responses.js";
|
|
@@ -8,7 +8,7 @@ import { ProviderID } from "../schema/index.js";
|
|
|
8
8
|
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js";
|
|
9
9
|
import * as OpenAIChat from "../protocols/openai-chat.js";
|
|
10
10
|
import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
|
|
11
|
-
import { isRecord
|
|
11
|
+
import { isRecord } from "../protocols/shared.js";
|
|
12
12
|
export const profile = OpenAICompatibleProfiles.profiles.openrouter;
|
|
13
13
|
export const id = ProviderID.make(profile.provider);
|
|
14
14
|
const ADAPTER = "openrouter";
|
|
@@ -39,12 +39,10 @@ export const protocol = Protocol.make({
|
|
|
39
39
|
reasoning_details: reasoningDetails,
|
|
40
40
|
};
|
|
41
41
|
});
|
|
42
|
-
const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
|
|
43
42
|
return {
|
|
44
43
|
...body,
|
|
45
44
|
messages,
|
|
46
45
|
...bodyOptions(request.providerOptions),
|
|
47
|
-
...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
|
|
48
46
|
};
|
|
49
47
|
})),
|
|
50
48
|
},
|
package/dist/providers/xai.js
CHANGED
|
@@ -21,7 +21,7 @@ const responsesRoute = Route.make({
|
|
|
21
21
|
name: "xAI Responses",
|
|
22
22
|
rotateAfterMs: RESPONSES_WEBSOCKET_ROTATE_AFTER_MS,
|
|
23
23
|
}),
|
|
24
|
-
defaults: { providerOptions: { store: false } },
|
|
24
|
+
defaults: { providerOptions: { store: false, include: ["reasoning.encrypted_content"] } },
|
|
25
25
|
});
|
|
26
26
|
const chatRoute = Route.make({
|
|
27
27
|
id: "openai-compatible-chat",
|
package/dist/route/client.js
CHANGED
|
@@ -5,6 +5,7 @@ import { RequestExecutor } from "./executor.js";
|
|
|
5
5
|
import { Framing } from "./framing.js";
|
|
6
6
|
import { HttpTransport } from "./transport/index.js";
|
|
7
7
|
import { applyCachePolicy } from "../cache-policy.js";
|
|
8
|
+
import { sanitizeSurrogates } from "../utils/sanitize.js";
|
|
8
9
|
import * as ProviderShared from "../protocols/shared.js";
|
|
9
10
|
import { AIError, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, InvalidProviderOutputReason, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
|
|
10
11
|
const makeRouteLanguageModel = (route, mapped) => {
|
|
@@ -159,7 +160,8 @@ export function make(input) {
|
|
|
159
160
|
});
|
|
160
161
|
}
|
|
161
162
|
const compile = Effect.fn("LLM.compile")(function* (request, options) {
|
|
162
|
-
const
|
|
163
|
+
const original = applyCachePolicy(resolveRequestOptions(request));
|
|
164
|
+
const resolved = LLMRequest.update(original, sanitizeSurrogates({ ...LLMRequest.input(original), model: undefined }));
|
|
163
165
|
const route = resolved.model.route;
|
|
164
166
|
const body = yield* route.body
|
|
165
167
|
.from(resolved)
|
package/dist/schema/options.d.ts
CHANGED
|
@@ -74,6 +74,7 @@ declare const LanguageModelCompatibility_base: Schema.Class<LanguageModelCompati
|
|
|
74
74
|
readonly reasoningField: Schema.optional<Schema.String>;
|
|
75
75
|
readonly maxTokensField: Schema.optional<Schema.Literals<readonly ["max_completion_tokens", "max_tokens"]>>;
|
|
76
76
|
readonly requireFinishReason: Schema.optional<Schema.Boolean>;
|
|
77
|
+
readonly requireAssistantAfterTool: Schema.optional<Schema.Boolean>;
|
|
77
78
|
readonly supportsStore: Schema.optional<Schema.Boolean>;
|
|
78
79
|
readonly supportsUsageInStreaming: Schema.optional<Schema.Boolean>;
|
|
79
80
|
readonly supportsStrictMode: Schema.optional<Schema.Boolean>;
|
package/dist/schema/options.js
CHANGED
|
@@ -101,6 +101,7 @@ export class LanguageModelCompatibility extends Schema.Class("LLM.LanguageModelC
|
|
|
101
101
|
reasoningField: Schema.optional(Schema.String),
|
|
102
102
|
maxTokensField: Schema.optional(LanguageModelMaxTokensFieldCompatibility),
|
|
103
103
|
requireFinishReason: Schema.optional(Schema.Boolean),
|
|
104
|
+
requireAssistantAfterTool: Schema.optional(Schema.Boolean),
|
|
104
105
|
supportsStore: Schema.optional(Schema.Boolean),
|
|
105
106
|
supportsUsageInStreaming: Schema.optional(Schema.Boolean),
|
|
106
107
|
supportsStrictMode: Schema.optional(Schema.Boolean),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sanitizeSurrogates: <T>(value: T) => T;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { isRecord } from "./record.js";
|
|
2
|
+
export const sanitizeSurrogates = (value) => {
|
|
3
|
+
if (typeof value === "string")
|
|
4
|
+
return value.toWellFormed();
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(sanitizeSurrogates);
|
|
7
|
+
if (value instanceof Uint8Array || value instanceof Error)
|
|
8
|
+
return value;
|
|
9
|
+
if (isRecord(value))
|
|
10
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key.toWellFormed(), sanitizeSurrogates(entry)]));
|
|
11
|
+
return value;
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-18230",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-rc.111",
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-beta-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-beta-18230",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.3.13",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@smithy/eventstream-codec": "4.2.14",
|
|
41
41
|
"@smithy/util-utf8": "4.2.2",
|
|
42
|
-
"@opencode-ai/schema": "0.0.0-beta-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-beta-18230",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.111",
|
|
45
45
|
"google-auth-library": "10.5.0"
|