@opencode-ai/ai 0.0.0-dev-18161 → 0.0.0-dev-18167
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.
|
@@ -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")
|
|
@@ -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
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-18167",
|
|
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-dev-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-dev-18167",
|
|
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-dev-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-dev-18167",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.111",
|
|
45
45
|
"google-auth-library": "10.5.0"
|