@opencode-ai/ai 0.0.0-beta-17759 → 0.0.0-beta-17793

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.
@@ -1,11 +1,13 @@
1
1
  import { Schema } from "effect";
2
2
  import { Route } from "../route/client.js";
3
+ import { Framing } from "../route/framing.js";
3
4
  import { Protocol } from "../route/protocol.js";
4
5
  import { Lifecycle } from "./utils/lifecycle.js";
5
6
  import { ToolStream } from "./utils/tool-stream.js";
6
7
  export declare const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
7
8
  export declare const PATH = "/messages";
8
9
  export declare const DEFAULT_MAX_TOKENS = 32000;
10
+ export declare const framing: Framing.Definition<string>;
9
11
  export type ThinkingInput = {
10
12
  readonly type: "adaptive";
11
13
  readonly display?: "summarized" | "omitted";
@@ -394,7 +396,7 @@ export declare const protocol: Protocol<{
394
396
  readonly [x: string]: unknown;
395
397
  readonly web_search_requests?: number | undefined;
396
398
  } | null | undefined;
397
- readonly input_tokens?: number | undefined;
399
+ readonly input_tokens?: number | null | undefined;
398
400
  readonly output_tokens?: number | undefined;
399
401
  readonly cache_creation_input_tokens?: number | null | undefined;
400
402
  readonly cache_read_input_tokens?: number | null | undefined;
@@ -424,7 +426,7 @@ export declare const protocol: Protocol<{
424
426
  readonly [x: string]: unknown;
425
427
  readonly web_search_requests?: number | undefined;
426
428
  } | null | undefined;
427
- readonly input_tokens?: number | undefined;
429
+ readonly input_tokens?: number | null | undefined;
428
430
  readonly output_tokens?: number | undefined;
429
431
  readonly cache_creation_input_tokens?: number | null | undefined;
430
432
  readonly cache_read_input_tokens?: number | null | undefined;
@@ -16,6 +16,17 @@ const ADAPTER = "anthropic-messages";
16
16
  export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
17
17
  export const PATH = "/messages";
18
18
  export const DEFAULT_MAX_TOKENS = 32_000;
19
+ const SSE_EVENTS = new Set([
20
+ "message",
21
+ "message_start",
22
+ "message_delta",
23
+ "message_stop",
24
+ "content_block_start",
25
+ "content_block_delta",
26
+ "content_block_stop",
27
+ "error",
28
+ ]);
29
+ export const framing = Framing.sseEvents(SSE_EVENTS);
19
30
  // =============================================================================
20
31
  // Request Body Schema
21
32
  // =============================================================================
@@ -163,7 +174,7 @@ const AnthropicBodyFields = {
163
174
  };
164
175
  export const AnthropicMessagesBody = Schema.Struct(AnthropicBodyFields);
165
176
  const AnthropicUsage = Schema.StructWithRest(Schema.Struct({
166
- input_tokens: Schema.optional(Schema.Number),
177
+ input_tokens: optionalNull(Schema.Number),
167
178
  output_tokens: Schema.optional(Schema.Number),
168
179
  cache_creation_input_tokens: optionalNull(Schema.Number),
169
180
  cache_read_input_tokens: optionalNull(Schema.Number),
@@ -259,15 +270,16 @@ const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("Anthropi
259
270
  required: () => ({ type: "any" }),
260
271
  tool: (name) => ({ type: "tool", name }),
261
272
  });
273
+ const scrubToolCallID = (id) => id.replace(/[^a-zA-Z0-9_-]/g, "_");
262
274
  const lowerToolCall = (part) => ({
263
275
  type: "tool_use",
264
- id: part.id,
276
+ id: scrubToolCallID(part.id),
265
277
  name: part.name,
266
278
  input: part.input,
267
279
  });
268
280
  const lowerServerToolCall = (part) => ({
269
281
  type: "server_tool_use",
270
- id: part.id,
282
+ id: scrubToolCallID(part.id),
271
283
  name: part.name,
272
284
  input: part.input,
273
285
  });
@@ -290,7 +302,7 @@ const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult
290
302
  // Prefer the provider-owned replay payload; fall back to the result value for
291
303
  // histories constructed directly from provider events.
292
304
  const payload = part.providerMetadata?.anthropic?.["result"] ?? part.result.value;
293
- return { type: wireType, tool_use_id: part.id, content: payload };
305
+ return { type: wireType, tool_use_id: scrubToolCallID(part.id), content: payload };
294
306
  });
295
307
  const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part) {
296
308
  const media = ProviderShared.normalizeMedia(part);
@@ -454,7 +466,7 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
454
466
  return yield* ProviderShared.unsupportedContent("Anthropic Messages", "tool", ["tool-result"]);
455
467
  content.push({
456
468
  type: "tool_result",
457
- tool_use_id: part.id,
469
+ tool_use_id: scrubToolCallID(part.id),
458
470
  content: yield* lowerToolResultContent(part),
459
471
  is_error: part.result.type === "error" ? true : undefined,
460
472
  cache_control: cacheControl(breakpoints, part.cache),
@@ -562,7 +574,7 @@ const mapFinishReason = (reason) => {
562
574
  const mapUsage = (usage) => {
563
575
  if (!usage)
564
576
  return undefined;
565
- const nonCached = usage.input_tokens;
577
+ const nonCached = usage.input_tokens ?? undefined;
566
578
  const cacheRead = usage.cache_read_input_tokens ?? undefined;
567
579
  const cacheWrite = usage.cache_creation_input_tokens ?? undefined;
568
580
  const inputTokens = ProviderShared.sumTokens(nonCached, cacheRead, cacheWrite);
@@ -870,7 +882,7 @@ export const route = Route.make({
870
882
  protocol,
871
883
  endpoint: Endpoint.path(PATH, { baseURL: DEFAULT_BASE_URL }),
872
884
  auth: Auth.none,
873
- framing: Framing.sse,
885
+ framing,
874
886
  headers: () => ({ "anthropic-version": "2023-06-01" }),
875
887
  });
876
888
  export * as AnthropicMessages from "./anthropic-messages.js";
@@ -290,7 +290,13 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
290
290
  },
291
291
  });
292
292
  }
293
- contents.push({ role: "user", parts });
293
+ // Gemini requires every response to a parallel call batch in one user turn,
294
+ // so consecutive tool results join the open function-response turn.
295
+ const previous = contents.at(-1);
296
+ if (previous?.role === "user" && previous.parts.some((item) => "functionResponse" in item))
297
+ contents[contents.length - 1] = { role: "user", parts: [...previous.parts, ...parts] };
298
+ else
299
+ contents.push({ role: "user", parts });
294
300
  }
295
301
  return contents;
296
302
  });
@@ -115,13 +115,14 @@ export declare const toolResultText: (part: ToolResultPart) => string;
115
115
  export declare const errorText: (error: unknown) => string;
116
116
  /**
117
117
  * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
118
- * decoder, and drops empty / `[DONE]` keep-alive events so the protocol event
119
- * schema sees one JSON string per element. The SSE channel emits a
118
+ * decoder, optionally filters named events, and drops empty / `[DONE]`
119
+ * keep-alive events so the protocol event schema sees one JSON string per
120
+ * element. The SSE channel emits a
120
121
  * `Retry` control event on its error channel; we drop it here (we don't
121
122
  * implement client-driven retries). Decoder failures become provider output
122
123
  * errors so the public error channel stays `AIError`.
123
124
  */
124
- export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<string, AIError>;
125
+ export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>, events?: ReadonlySet<string>) => Stream.Stream<string, AIError>;
125
126
  /**
126
127
  * Canonical invalid-request constructor shared by protocol lowering.
127
128
  */
@@ -149,13 +149,16 @@ export const errorText = (error) => {
149
149
  };
150
150
  /**
151
151
  * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
152
- * decoder, and drops empty / `[DONE]` keep-alive events so the protocol event
153
- * schema sees one JSON string per element. The SSE channel emits a
152
+ * decoder, optionally filters named events, and drops empty / `[DONE]`
153
+ * keep-alive events so the protocol event schema sees one JSON string per
154
+ * element. The SSE channel emits a
154
155
  * `Retry` control event on its error channel; we drop it here (we don't
155
156
  * implement client-driven retries). Decoder failures become provider output
156
157
  * errors so the public error channel stays `AIError`.
157
158
  */
158
- export const sseFraming = (bytes) => bytes.pipe(Stream.decodeText(), Stream.pipeThroughChannel(Sse.decode()), Stream.catchTag("Retry", () => Stream.empty), Stream.catchTag("SseError", (error) => Stream.fail(eventError("sse", error.message))), Stream.filter((event) => event.data.length > 0 && event.data !== "[DONE]"), Stream.map((event) => event.data));
159
+ export const sseFraming = (bytes, events) => bytes.pipe(Stream.decodeText(), Stream.pipeThroughChannel(Sse.decode()), Stream.catchTag("Retry", () => Stream.empty), Stream.catchTag("SseError", (error) => Stream.fail(eventError("sse", error.message))), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
160
+ event.data.length > 0 &&
161
+ (event.data !== "[DONE]" || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
159
162
  /**
160
163
  * Canonical invalid-request constructor shared by protocol lowering.
161
164
  */
@@ -52,6 +52,7 @@ const INVALID_REQUEST_CODES = new Set(["invalid_prompt", "invalid_request_error"
52
52
  const RATE_LIMIT_TEXT = /rate increased too quickly|rate[-_\s]?limit|too[_\s]?many[_\s]?requests/i;
53
53
  const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded/i;
54
54
  const CONTENT_POLICY_TEXT = /content[-_\s]?policy|content_filter|safety/i;
55
+ const NETWORK_ERROR_TEXT = /network[-_\s]error/i;
55
56
  // Keep HTTP failures and provider-reported stream failures on one typed path so
56
57
  // session retry policy never needs provider-specific string matching.
57
58
  export function classifyProviderFailure(input) {
@@ -93,6 +94,8 @@ export function classifyProviderFailure(input) {
93
94
  retryAfterMs: input.retryAfterMs,
94
95
  rateLimit: input.rateLimit,
95
96
  });
97
+ if (NETWORK_ERROR_TEXT.test(text))
98
+ return new ProviderInternalReason({ ...common, status: input.status });
96
99
  if (codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))
97
100
  return new ProviderInternalReason({
98
101
  ...common,
@@ -3,11 +3,11 @@ import { AnthropicMessages } from "../protocols/anthropic-messages.js";
3
3
  import { Auth } from "../route/auth.js";
4
4
  import { Route } from "../route/client.js";
5
5
  import { Endpoint } from "../route/endpoint.js";
6
- import { Framing } from "../route/framing.js";
7
6
  import { Protocol } from "../route/protocol.js";
8
7
  import { ProviderID } from "../schema/index.js";
9
8
  import { GoogleVertexShared } from "./google-vertex-shared.js";
10
9
  const VERSION = "vertex-2023-10-16";
10
+ const HEADER_VERSION = "2023-06-01";
11
11
  export const id = ProviderID.make("google-vertex");
12
12
  const route = Route.make({
13
13
  id: "google-vertex-messages",
@@ -29,7 +29,8 @@ const route = Route.make({
29
29
  }),
30
30
  endpoint: Endpoint.path(({ request }) => `/${request.model.id}:streamRawPredict`),
31
31
  auth: Auth.none,
32
- framing: Framing.sse,
32
+ framing: AnthropicMessages.framing,
33
+ headers: () => ({ "anthropic-version": HEADER_VERSION }),
33
34
  });
34
35
  export const routes = [route];
35
36
  const configuredRoute = (input) => {
@@ -20,4 +20,6 @@ export interface Definition<Frame> {
20
20
  }
21
21
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
22
22
  export declare const sse: Definition<string>;
23
+ /** SSE framing restricted to protocol-recognized event names. */
24
+ export declare const sseEvents: (events: ReadonlySet<string>) => Definition<string>;
23
25
  export * as Framing from "./framing.js";
@@ -1,4 +1,9 @@
1
1
  import * as ProviderShared from "../protocols/shared.js";
2
2
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
3
3
  export const sse = { id: "sse", frame: ProviderShared.sseFraming };
4
+ /** SSE framing restricted to protocol-recognized event names. */
5
+ export const sseEvents = (events) => ({
6
+ id: "sse",
7
+ frame: (bytes) => ProviderShared.sseFraming(bytes, events),
8
+ });
4
9
  export * as Framing from "./framing.js";
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-17759",
3
+ "version": "0.0.0-beta-17793",
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.110",
33
- "@opencode-ai/http-recorder": "0.0.0-beta-17759",
33
+ "@opencode-ai/http-recorder": "0.0.0-beta-17793",
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-17759",
42
+ "@opencode-ai/schema": "0.0.0-beta-17793",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.110",
45
45
  "google-auth-library": "10.5.0"