@opencode-ai/ai 0.0.0-next-15809 → 0.0.0-next-15811

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.
@@ -14,6 +14,7 @@ const withoutTool = (tools, key) => {
14
14
  const inputStart = (tool) => LLMEvent.toolInputStart({
15
15
  id: tool.id,
16
16
  name: tool.name,
17
+ providerExecuted: tool.providerExecuted ? true : undefined,
17
18
  providerMetadata: tool.providerMetadata,
18
19
  });
19
20
  const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
@@ -21,13 +22,27 @@ const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
21
22
  name: tool.name,
22
23
  text,
23
24
  });
24
- const toolCall = (route, tool, inputOverride) => parseToolInput(route, tool.name, inputOverride ?? tool.input).pipe(Effect.map((input) => LLMEvent.toolCall({
25
- id: tool.id,
26
- name: tool.name,
27
- input,
28
- providerExecuted: tool.providerExecuted ? true : undefined,
29
- providerMetadata: tool.providerMetadata,
30
- })));
25
+ const toolCall = (route, tool, inputOverride) => {
26
+ const raw = inputOverride ?? tool.input;
27
+ return parseToolInput(route, tool.name, raw).pipe(Effect.map((input) => LLMEvent.toolCall({
28
+ id: tool.id,
29
+ name: tool.name,
30
+ input,
31
+ providerExecuted: tool.providerExecuted ? true : undefined,
32
+ providerMetadata: tool.providerMetadata,
33
+ })), Effect.catch((error) => tool.providerExecuted
34
+ ? Effect.fail(error)
35
+ : Effect.succeed(LLMEvent.toolInputError({
36
+ id: tool.id,
37
+ name: tool.name,
38
+ raw,
39
+ message: error.reason.message,
40
+ providerMetadata: tool.providerMetadata,
41
+ }))));
42
+ };
43
+ const finishEvents = (tool, event) => event.type === "tool-input-error"
44
+ ? [event]
45
+ : [LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), event];
31
46
  /** Store the updated tool and produce the optional public delta event. */
32
47
  const appendTool = (tools, key, tool, text) => {
33
48
  const events = [];
@@ -86,8 +101,9 @@ export const appendExisting = (route, tools, key, text, missingToolMessage) => {
86
101
  };
87
102
  /**
88
103
  * Finalize one pending tool call: parse the accumulated raw JSON, remove it
89
- * from state, and return the optional public `tool-call` event. Missing keys are
90
- * a no-op because some providers emit stop events for non-tool content blocks.
104
+ * from state, and return either a call or a non-executable local input error.
105
+ * Missing keys are a no-op because some providers emit stop events for
106
+ * non-tool content blocks.
91
107
  */
92
108
  export const finish = (route, tools, key) => Effect.gen(function* () {
93
109
  const tool = tools[key];
@@ -95,10 +111,7 @@ export const finish = (route, tools, key) => Effect.gen(function* () {
95
111
  return { tools };
96
112
  return {
97
113
  tools: withoutTool(tools, key),
98
- events: [
99
- LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
100
- yield* toolCall(route, tool),
101
- ],
114
+ events: finishEvents(tool, yield* toolCall(route, tool)),
102
115
  };
103
116
  });
104
117
  /**
@@ -112,25 +125,19 @@ export const finishWithInput = (route, tools, key, input) => Effect.gen(function
112
125
  return { tools };
113
126
  return {
114
127
  tools: withoutTool(tools, key),
115
- events: [
116
- LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
117
- yield* toolCall(route, tool, input),
118
- ],
128
+ events: finishEvents(tool, yield* toolCall(route, tool, input)),
119
129
  };
120
130
  });
121
131
  /**
122
132
  * Finalize every pending tool call at once. OpenAI Chat has this shape: it does
123
- * not emit per-tool stop events, so all accumulated calls finish when the choice
124
- * receives a terminal `finish_reason`.
133
+ * not emit per-tool stop events, so all accumulated calls finish independently
134
+ * when the choice receives a terminal `finish_reason`.
125
135
  */
126
136
  export const finishAll = (route, tools) => Effect.gen(function* () {
127
137
  const pending = Object.values(tools).filter((tool) => tool !== undefined);
128
138
  return {
129
139
  tools: empty(),
130
- events: yield* Effect.forEach(pending, (tool) => toolCall(route, tool).pipe(Effect.map((call) => [
131
- LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
132
- call,
133
- ]))).pipe(Effect.map((events) => events.flat())),
140
+ events: yield* Effect.forEach(pending, (tool) => toolCall(route, tool).pipe(Effect.map((event) => finishEvents(tool, event)))).pipe(Effect.map((events) => events.flat())),
134
141
  };
135
142
  });
136
143
  export * as ToolStream from "./tool-stream";
@@ -208,6 +208,7 @@ export declare const streamRequest: (request: LLMRequest) => Stream.Stream<Schem
208
208
  readonly [x: string]: unknown;
209
209
  };
210
210
  } | undefined;
211
+ readonly providerExecuted?: boolean | undefined;
211
212
  } | Schema.Struct.ReadonlySide<{
212
213
  readonly type: Schema.tag<"tool-input-delta">;
213
214
  readonly id: Schema.String;
@@ -222,6 +223,17 @@ export declare const streamRequest: (request: LLMRequest) => Stream.Stream<Schem
222
223
  readonly [x: string]: unknown;
223
224
  };
224
225
  } | undefined;
226
+ } | {
227
+ readonly type: "tool-input-error";
228
+ readonly name: string;
229
+ readonly id: string;
230
+ readonly message: string;
231
+ readonly raw: string;
232
+ readonly providerMetadata?: {
233
+ readonly [x: string]: {
234
+ readonly [x: string]: unknown;
235
+ };
236
+ } | undefined;
225
237
  } | {
226
238
  readonly type: "tool-call";
227
239
  readonly name: string;