@opencode/ai 2.0.25 → 2.0.27

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.
Files changed (93) hide show
  1. package/README.md +7 -1
  2. package/dist/experimental/system-one.js +1 -1
  3. package/dist/media.js +7 -6
  4. package/dist/protocols/anthropic-messages.js +1 -1
  5. package/dist/protocols/bedrock-converse.js +4 -3
  6. package/dist/protocols/bedrock-event-stream.js +4 -3
  7. package/dist/protocols/meta-responses.js +3 -2
  8. package/dist/protocols/mistral-chat.d.ts +1 -1
  9. package/dist/protocols/mistral-chat.js +1 -1
  10. package/dist/protocols/open-responses-channel.d.ts +1 -1
  11. package/dist/protocols/open-responses-channel.js +1 -1
  12. package/dist/protocols/openai-responses.d.ts +5 -5
  13. package/dist/protocols/openai-responses.js +4 -3
  14. package/dist/protocols/shared.d.ts +1 -1
  15. package/dist/protocols/shared.js +1 -1
  16. package/dist/protocols/utils/bedrock-auth.js +1 -1
  17. package/dist/protocols/utils/bedrock-media.js +4 -3
  18. package/dist/protocols/utils/fal-queue.d.ts +1 -1
  19. package/dist/protocols/utils/media-input.js +4 -3
  20. package/dist/protocols/utils/tool-schema.js +8 -2
  21. package/dist/providers/amazon-bedrock-mantle.js +2 -2
  22. package/dist/providers/anthropic-compatible.js +2 -2
  23. package/dist/providers/anthropic.js +2 -2
  24. package/dist/providers/azure.d.ts +2 -2
  25. package/dist/providers/azure.js +3 -3
  26. package/dist/providers/baseten.js +2 -2
  27. package/dist/providers/cerebras.js +2 -2
  28. package/dist/providers/cloudflare-ai-gateway.d.ts +2 -2
  29. package/dist/providers/cloudflare-ai-gateway.js +2 -2
  30. package/dist/providers/cloudflare-workers-ai.js +2 -2
  31. package/dist/providers/cohere/chat.js +1 -1
  32. package/dist/providers/cohere.js +1 -1
  33. package/dist/providers/deepinfra.js +2 -2
  34. package/dist/providers/deepseek.js +2 -2
  35. package/dist/providers/fireworks.js +2 -2
  36. package/dist/providers/google/interactions.js +2 -2
  37. package/dist/providers/google-vertex/interactions.d.ts +2 -0
  38. package/dist/providers/google-vertex/interactions.js +1 -0
  39. package/dist/providers/google-vertex/mistral.d.ts +2 -0
  40. package/dist/providers/google-vertex/mistral.js +1 -0
  41. package/dist/providers/google-vertex-chat.js +3 -3
  42. package/dist/providers/google-vertex-interactions.d.ts +112 -0
  43. package/dist/providers/google-vertex-interactions.js +64 -0
  44. package/dist/providers/google-vertex-messages.js +3 -3
  45. package/dist/providers/google-vertex-mistral.d.ts +124 -0
  46. package/dist/providers/google-vertex-mistral.js +69 -0
  47. package/dist/providers/google-vertex-responses.js +3 -3
  48. package/dist/providers/google-vertex-shared.d.ts +7 -2
  49. package/dist/providers/google-vertex-shared.js +36 -4
  50. package/dist/providers/google-vertex.js +3 -3
  51. package/dist/providers/google.js +2 -2
  52. package/dist/providers/groq.js +2 -2
  53. package/dist/providers/index.d.ts +2 -0
  54. package/dist/providers/index.js +2 -0
  55. package/dist/providers/meta.js +1 -1
  56. package/dist/providers/minimax/chat.js +1 -1
  57. package/dist/providers/minimax/responses.js +1 -1
  58. package/dist/providers/minimax.js +1 -1
  59. package/dist/providers/mistral.js +2 -2
  60. package/dist/providers/moonshot/messages.js +1 -1
  61. package/dist/providers/moonshot/responses.js +1 -1
  62. package/dist/providers/moonshot.js +1 -1
  63. package/dist/providers/openai-compatible-responses.js +2 -2
  64. package/dist/providers/openai-compatible.js +2 -2
  65. package/dist/providers/openai.d.ts +1 -1
  66. package/dist/providers/openai.js +2 -2
  67. package/dist/providers/openrouter.js +1 -1
  68. package/dist/providers/togetherai.js +2 -2
  69. package/dist/providers/venice.js +1 -1
  70. package/dist/providers/vercel-ai-gateway.js +2 -2
  71. package/dist/providers/xai.js +1 -1
  72. package/dist/providers/zai-coding-plan/messages.js +1 -1
  73. package/dist/providers/zai-coding-plan/responses.js +1 -1
  74. package/dist/providers/zai-coding-plan.js +1 -1
  75. package/dist/providers/zai.js +1 -1
  76. package/dist/route/auth.d.ts +1 -1
  77. package/dist/route/auth.js +2 -2
  78. package/dist/route/executor-service.d.ts +1 -1
  79. package/dist/route/executor.d.ts +1 -1
  80. package/dist/route/executor.js +1 -1
  81. package/dist/route/framing.js +1 -1
  82. package/dist/route/media-protocol.d.ts +1 -1
  83. package/dist/route/media-protocol.js +1 -1
  84. package/dist/route/media.js +1 -1
  85. package/dist/route/transport/http.d.ts +1 -1
  86. package/dist/route/transport/http.js +1 -1
  87. package/dist/route/transport/websocket-channel.d.ts +1 -1
  88. package/dist/route/transport/websocket.d.ts +4 -4
  89. package/dist/route/transport/websocket.js +4 -9
  90. package/dist/schema/messages.js +9 -8
  91. package/dist/tool.d.ts +10 -0
  92. package/dist/tool.js +21 -2
  93. package/package.json +5 -5
@@ -25,8 +25,8 @@ export const provider = {
25
25
  export const model = (modelID, { apiKey, baseURL, body, headers, provider, ...providerOptions }) => configure({
26
26
  apiKey,
27
27
  baseURL,
28
- headers: headers === undefined ? undefined : { ...headers },
29
- http: body === undefined ? undefined : { body: { ...body } },
28
+ headers,
29
+ http: body === undefined ? undefined : { body },
30
30
  provider,
31
31
  providerOptions,
32
32
  }).model(modelID);
@@ -25,8 +25,8 @@ export const provider = {
25
25
  export const model = (modelID, { apiKey, baseURL, body, headers, provider, ...providerOptions }) => configure({
26
26
  apiKey,
27
27
  baseURL,
28
- headers: headers === undefined ? undefined : { ...headers },
29
- http: body === undefined ? undefined : { body: { ...body } },
28
+ headers,
29
+ http: body === undefined ? undefined : { body },
30
30
  provider,
31
31
  providerOptions,
32
32
  }).model(modelID);
@@ -275,7 +275,7 @@ export declare const routes: (Route<{
275
275
  } | {
276
276
  readonly type: "image_generation";
277
277
  readonly size?: string | undefined;
278
- readonly action?: "generate" | "auto" | "edit" | undefined;
278
+ readonly action?: "auto" | "generate" | "edit" | undefined;
279
279
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
280
280
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
281
281
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
@@ -77,9 +77,9 @@ const config = ({ apiKey, baseURL, body, headers: given, organization, project,
77
77
  apiKey,
78
78
  baseURL,
79
79
  headers: Object.keys(headers).length === 0 ? undefined : headers,
80
- http: body === undefined ? undefined : { body: { ...body } },
80
+ http: body === undefined ? undefined : { body },
81
81
  providerOptions,
82
- queryParams: queryParams === undefined ? undefined : { ...queryParams },
82
+ queryParams,
83
83
  };
84
84
  };
85
85
  export const model = (modelID, settings) => {
@@ -114,6 +114,6 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
114
114
  apiKey,
115
115
  baseURL,
116
116
  headers,
117
- http: body === undefined ? undefined : { body: { ...body } },
117
+ http: body === undefined ? undefined : { body },
118
118
  providerOptions,
119
119
  }).model(modelID);
@@ -34,7 +34,7 @@ export const provider = configure();
34
34
  export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOptions }) => configure({
35
35
  apiKey,
36
36
  baseURL,
37
- headers: headers === undefined ? undefined : { ...headers },
38
- http: body === undefined ? undefined : { body: { ...body } },
37
+ headers,
38
+ http: body === undefined ? undefined : { body },
39
39
  providerOptions,
40
40
  }).model(modelID);
@@ -32,7 +32,7 @@ export const model = (modelID, { apiKey, baseURL, queryParams, body, headers, ..
32
32
  baseURL,
33
33
  queryParams,
34
34
  headers,
35
- http: body === undefined ? undefined : { body: { ...body } },
35
+ http: body === undefined ? undefined : { body },
36
36
  providerOptions,
37
37
  }).model(modelID);
38
38
  export * as Venice from "./venice.js";
@@ -1,5 +1,5 @@
1
1
  import { Effect, Schema } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
2
+ import { Headers, HttpClientRequest } from "effect/http";
3
3
  import { EvaluationAnswer, EvaluationInput, EvaluationModel, EvaluationQuestion, EvaluationResponse, EvaluationRounding, } from "../experimental/evaluation.js";
4
4
  import { AnthropicMessages } from "../protocols/anthropic-messages.js";
5
5
  import { OpenAIChat } from "../protocols/openai-chat.js";
@@ -218,7 +218,7 @@ function fromSettings({ apiKey, baseURL, headers, body, ...providerOptions }) {
218
218
  apiKey,
219
219
  baseURL,
220
220
  headers,
221
- http: body === undefined ? undefined : { body: { ...body } },
221
+ http: body === undefined ? undefined : { body },
222
222
  providerOptions,
223
223
  });
224
224
  }
@@ -77,7 +77,7 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
77
77
  apiKey,
78
78
  baseURL,
79
79
  headers,
80
- http: body === undefined ? undefined : { body: { ...body } },
80
+ http: body === undefined ? undefined : { body },
81
81
  providerOptions,
82
82
  }).model(modelID);
83
83
  export const responses = provider.responses;
@@ -3,6 +3,6 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
3
3
  apiKey,
4
4
  baseURL,
5
5
  headers,
6
- http: body === undefined ? undefined : { body: { ...body } },
6
+ http: body === undefined ? undefined : { body },
7
7
  providerOptions,
8
8
  }).messages(modelID);
@@ -3,6 +3,6 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
3
3
  apiKey,
4
4
  baseURL,
5
5
  headers,
6
- http: body === undefined ? undefined : { body: { ...body } },
6
+ http: body === undefined ? undefined : { body },
7
7
  providerOptions,
8
8
  }).responses(modelID);
@@ -57,7 +57,7 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
57
57
  apiKey,
58
58
  baseURL,
59
59
  headers,
60
- http: body === undefined ? undefined : { body: { ...body } },
60
+ http: body === undefined ? undefined : { body },
61
61
  providerOptions,
62
62
  }).model(modelID);
63
63
  export * as ZAICodingPlan from "./zai-coding-plan.js";
@@ -43,7 +43,7 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
43
43
  apiKey,
44
44
  baseURL,
45
45
  headers,
46
- http: body === undefined ? undefined : { body: { ...body } },
46
+ http: body === undefined ? undefined : { body },
47
47
  providerOptions,
48
48
  }).model(modelID);
49
49
  export * as ZAI from "./zai.js";
@@ -1,5 +1,5 @@
1
1
  import { Config, Effect, Redacted } from "effect";
2
- import { Headers } from "effect/unstable/http";
2
+ import { Headers } from "effect/http";
3
3
  import { AIError, type HttpOptions } from "../schema/index.js";
4
4
  export declare class MissingCredentialError extends Error {
5
5
  readonly source: string;
@@ -1,5 +1,5 @@
1
1
  import { Config, Effect, Option, Redacted } from "effect";
2
- import { Headers } from "effect/unstable/http";
2
+ import { Headers } from "effect/http";
3
3
  import { AuthenticationError, AIError } from "../schema/index.js";
4
4
  export class MissingCredentialError extends Error {
5
5
  source;
@@ -48,7 +48,7 @@ export const optional = (secret, source = "optional value") => secret === undefi
48
48
  ? credential(Effect.fail(new MissingCredentialError(source)))
49
49
  : credentialFromSecret(secret, source);
50
50
  export const config = (name) => credential(Effect.gen(function* () {
51
- const secret = yield* Config.option(Config.redacted(name));
51
+ const secret = yield* Config.option(Config.Redacted(name));
52
52
  if (Option.isSome(secret) && Redacted.value(secret.value) !== "")
53
53
  return secret.value;
54
54
  return yield* Effect.fail(new MissingCredentialError(name, `${name} is not set`));
@@ -1,5 +1,5 @@
1
1
  import { Context, type Effect } from "effect";
2
- import type { HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
2
+ import type { HttpClientRequest, HttpClientResponse } from "effect/http";
3
3
  import type { AIError } from "../schema/errors.js";
4
4
  export interface Interface {
5
5
  readonly execute: (request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;
@@ -1,5 +1,5 @@
1
1
  import { Layer, Stream } from "effect";
2
- import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
2
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
3
3
  import { HttpContext, AIError } from "../schema/index.js";
4
4
  import { Service, type HttpMiddleware, type Interface } from "./executor-service.js";
5
5
  export { Service } from "./executor-service.js";
@@ -1,5 +1,5 @@
1
1
  import { Cause, Effect, Layer, Option, Schema, Stream } from "effect";
2
- import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
2
+ import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/http";
3
3
  import { HttpContext, HttpRateLimitDetails, AIError, TransportError } from "../schema/index.js";
4
4
  import { classifyProviderFailure, providerErrorMessage } from "../provider-error.js";
5
5
  import { Service } from "./executor-service.js";
@@ -1,5 +1,5 @@
1
1
  import { Effect, Stream } from "effect";
2
- import { makeParser } from "effect/unstable/encoding/Sse";
2
+ import { makeParser } from "effect/encoding/Sse";
3
3
  import { AIError, InvalidProviderOutputError } from "../schema/index.js";
4
4
  /**
5
5
  * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
@@ -1,5 +1,5 @@
1
1
  import { Duration, Effect, Schema, type Stream } from "effect";
2
- import { HttpClientResponse } from "effect/unstable/http";
2
+ import { HttpClientResponse } from "effect/http";
3
3
  import type { Snapshot, Status } from "../generation.js";
4
4
  import { Media } from "../media.js";
5
5
  import type { AuthInput } from "./auth.js";
@@ -1,5 +1,5 @@
1
1
  import { Clock, Duration, Effect, Schema } from "effect";
2
- import { HttpClientResponse } from "effect/unstable/http";
2
+ import { HttpClientResponse } from "effect/http";
3
3
  import { Media } from "../media.js";
4
4
  import { AIError, AuthenticationError, ContentPolicyError, HttpContext, InvalidProviderOutputError, InvalidRequestError, ProviderID, ProviderInternalError, RateLimitError, UnsupportedOperationError, } from "../schema/index.js";
5
5
  export const json = (value, query) => ({
@@ -1,5 +1,5 @@
1
1
  import { Duration, Effect, Schedule, Schema, Stream } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
2
+ import { Headers, HttpClientRequest } from "effect/http";
3
3
  import { Auth } from "./auth.js";
4
4
  import { Endpoint } from "./endpoint.js";
5
5
  import { RequestExecutorService } from "./executor-service.js";
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
2
+ import { Headers, HttpClientRequest } from "effect/http";
3
3
  import { Framing } from "../framing.js";
4
4
  import type { HttpMiddleware, Transport, TransportPrepareInput } from "./index.js";
5
5
  import { AIError } from "../../schema/index.js";
@@ -1,5 +1,5 @@
1
1
  import { Clock, Duration, Effect, Stream } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
2
+ import { Headers, HttpClientRequest } from "effect/http";
3
3
  import { Auth } from "../auth.js";
4
4
  import { render as renderEndpoint } from "../endpoint.js";
5
5
  import { Framing } from "../framing.js";
@@ -1,5 +1,5 @@
1
1
  import type { Effect, Scope, Stream } from "effect";
2
- import type { Headers } from "effect/unstable/http";
2
+ import type { Headers } from "effect/http";
3
3
  import type { AIError, HttpContext } from "../../schema/index.js";
4
4
  export interface WebSocketChannelExecutor {
5
5
  readonly execute: (exchange: WebSocketChannelExchange) => Effect.Effect<WebSocketChannelExecution, AIError, Scope.Scope>;
@@ -1,6 +1,6 @@
1
1
  import { Effect, Stream } from "effect";
2
- import { Headers } from "effect/unstable/http";
3
- import { Socket } from "effect/unstable/socket";
2
+ import { Headers } from "effect/http";
3
+ import { Socket } from "effect/socket";
4
4
  import { AIError, type HttpContext } from "../../schema/index.js";
5
5
  import type { Transport } from "./index.js";
6
6
  import type { WebSocketChannelExecutor } from "./websocket-channel.js";
@@ -19,7 +19,7 @@ export interface WebSocketConnector {
19
19
  }
20
20
  export declare const toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
21
21
  export declare const open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
22
- export declare const fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
22
+ export declare const fromWebSocket: (ws: Socket.WebSocketLike, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
23
23
  export declare const messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
24
24
  export declare const makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
25
25
  export declare const direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
@@ -50,7 +50,7 @@ export declare const WebSocketTransport: {
50
50
  readonly direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
51
51
  readonly makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
52
52
  readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
53
- readonly fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
53
+ readonly fromWebSocket: (ws: Socket.WebSocketLike, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
54
54
  readonly messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
55
55
  readonly toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
56
56
  };
@@ -1,6 +1,6 @@
1
1
  import { Cause, Effect, Queue, Stream } from "effect";
2
- import { Headers } from "effect/unstable/http";
3
- import { Socket } from "effect/unstable/socket";
2
+ import { Headers } from "effect/http";
3
+ import { Socket } from "effect/socket";
4
4
  import { AIError, AIErrorReason, TransportError, } from "../../schema/index.js";
5
5
  import * as HttpTransport from "./http.js";
6
6
  const MAX_FRAME_BYTES = 16 * 1024 * 1024;
@@ -31,7 +31,7 @@ const annotateTransportError = (error, input) => error.reason._tag === "Transpor
31
31
  const eventMessage = (event) => {
32
32
  if ("message" in event && typeof event.message === "string")
33
33
  return event.message;
34
- return event.type;
34
+ return event.type ?? "error";
35
35
  };
36
36
  const binaryMessage = (data) => {
37
37
  if (data instanceof Uint8Array)
@@ -127,12 +127,7 @@ export const toWebSocketUrl = (value) => Effect.try({
127
127
  export const open = (input) => Effect.gen(function* () {
128
128
  const constructor = yield* Socket.WebSocketConstructor;
129
129
  const ws = yield* Effect.try({
130
- try: () =>
131
- // Platform implementations may extend Effect's browser-compatible constructor with handshake options.
132
- // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
133
- constructor(input.url, {
134
- headers: input.headers,
135
- }),
130
+ try: () => constructor(input.url, { headers: input.headers }),
136
131
  catch: (error) => transportError(error instanceof Error ? error.message : "Failed to construct WebSocket", {
137
132
  cause: error,
138
133
  url: input.url,
@@ -1,4 +1,5 @@
1
1
  import { Schema } from "effect";
2
+ import { withStatics } from "@opencode/schema/schema";
2
3
  import { Tool } from "@opencode/schema/tool";
3
4
  import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, JsonSchema, LanguageModelSchema, ProviderMetadata, ProviderOptions, ReasoningEffort, } from "./options.js";
4
5
  import { ProviderID } from "./ids.js";
@@ -11,7 +12,7 @@ const systemPartSchema = Schema.Struct({
11
12
  metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
12
13
  }).annotate({ identifier: "LLM.SystemPart" });
13
14
  const makeSystemPart = (text) => ({ type: "text", text });
14
- export const SystemPart = Object.assign(systemPartSchema, {
15
+ export const SystemPart = withStatics(systemPartSchema, {
15
16
  make: makeSystemPart,
16
17
  content: (input) => {
17
18
  if (input === undefined)
@@ -53,7 +54,7 @@ const toolResultValueSchema = Schema.Union([
53
54
  }),
54
55
  ]).annotate({ identifier: "LLM.ToolResult" });
55
56
  const isToolResultValue = Schema.is(toolResultValueSchema);
56
- export const ToolResultValue = Object.assign(toolResultValueSchema, {
57
+ export const ToolResultValue = withStatics(toolResultValueSchema, {
57
58
  is: isToolResultValue,
58
59
  make: (value, type = "json") => {
59
60
  if (isToolResultValue(value))
@@ -63,7 +64,7 @@ export const ToolResultValue = Object.assign(toolResultValueSchema, {
63
64
  return { type, value };
64
65
  },
65
66
  });
66
- export const ToolOutput = Object.assign(Schema.Struct({
67
+ export const ToolOutput = withStatics(Schema.Struct({
67
68
  structured: Schema.Unknown,
68
69
  content: Schema.Array(Tool.Content),
69
70
  }).annotate({ identifier: "LLM.ToolOutput" }), {
@@ -98,7 +99,7 @@ const toolResultText = (value) => {
98
99
  return String(value);
99
100
  }
100
101
  };
101
- export const ToolCallPart = Object.assign(Schema.Struct({
102
+ export const ToolCallPart = withStatics(Schema.Struct({
102
103
  type: Schema.Literal("tool-call"),
103
104
  id: Schema.String,
104
105
  name: Schema.String,
@@ -111,7 +112,7 @@ export const ToolCallPart = Object.assign(Schema.Struct({
111
112
  }).annotate({ identifier: "LLM.Content.ToolCall" }), {
112
113
  make: (input) => ({ type: "tool-call", ...input }),
113
114
  });
114
- export const ToolResultPart = Object.assign(Schema.Struct({
115
+ export const ToolResultPart = withStatics(Schema.Struct({
115
116
  type: Schema.Literal("tool-result"),
116
117
  id: Schema.String,
117
118
  name: Schema.String,
@@ -152,7 +153,7 @@ const compactionPartSchema = Schema.Struct({
152
153
  })
153
154
  .pipe(Schema.refine((part) => (part.encrypted !== undefined) !== (part.text !== undefined), { message: "Compaction requires either encrypted content or a summary" }))
154
155
  .annotate({ identifier: "LLM.Content.Compaction" });
155
- export const CompactionPart = Object.assign(compactionPartSchema, {
156
+ export const CompactionPart = withStatics(compactionPartSchema, {
156
157
  make: (input) => Schema.decodeUnknownSync(compactionPartSchema)({ type: "compaction", ...input }),
157
158
  });
158
159
  /** Reasoning effort changed here, from `previous` to `effort`; `undefined` is the model default. */
@@ -225,7 +226,7 @@ export class ToolDefinition extends Schema.Class("LLM.ToolDefinition")({
225
226
  /** Normalize tool definition input into the canonical `ToolDefinition` class. */
226
227
  ToolDefinition.make = (input) => (input instanceof ToolDefinition ? input : new ToolDefinition(input));
227
228
  })(ToolDefinition || (ToolDefinition = {}));
228
- export const ToolNamespace = Object.assign(Schema.Struct({
229
+ export const ToolNamespace = withStatics(Schema.Struct({
229
230
  type: Schema.Literal("namespace"),
230
231
  name: Schema.String,
231
232
  description: Schema.optional(Schema.UndefinedOr(Schema.String)),
@@ -237,7 +238,7 @@ export const ToolNamespace = Object.assign(Schema.Struct({
237
238
  tools: input.tools.map(ToolEntry.make),
238
239
  }),
239
240
  });
240
- export const ToolEntry = Object.assign(Schema.Union([ToolDefinition, ToolNamespace]).pipe(Schema.toTaggedUnion("type"), Schema.annotate({ identifier: "LLM.ToolEntry" })), {
241
+ export const ToolEntry = withStatics(Schema.Union([ToolDefinition, ToolNamespace]).pipe(Schema.toTaggedUnion("type"), Schema.annotate({ identifier: "LLM.ToolEntry" })), {
241
242
  make: (input) => "type" in input && input.type === "namespace" ? ToolNamespace.make(input) : ToolDefinition.make(input),
242
243
  });
243
244
  export class ToolChoice extends Schema.Class("LLM.ToolChoice")({
package/dist/tool.d.ts CHANGED
@@ -132,5 +132,15 @@ export type Tools = Record<string, AnyTool>;
132
132
  * is reused.
133
133
  */
134
134
  export declare const toDefinitions: (tools: Tools) => ReadonlyArray<ToolDefinitionClass>;
135
+ /**
136
+ * Describes a no-argument Effect tool input as an empty object, or returns
137
+ * `undefined` when the input takes arguments.
138
+ *
139
+ * Effect emits `{ not: { type: "null" } }` only for an encoded struct with no
140
+ * properties or index signatures, but providers require tool parameters to
141
+ * describe an object. Annotations are kept; any other keyword, such as a
142
+ * check's `minProperties`, leaves the schema as Effect emitted it.
143
+ */
144
+ export declare const emptyInputJsonSchema: (schema: Schema.Top) => JsonSchema.JsonSchema | undefined;
135
145
  export { ToolFailure };
136
146
  export * as Tool from "./tool.js";
package/dist/tool.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Effect, JsonSchema, Schema } from "effect";
2
2
  import { Tool } from "@opencode/schema/tool";
3
3
  import { ToolDefinition, ToolFailure, ToolOutput } from "./schema/index.js";
4
+ import { isRecord } from "./utils/record.js";
4
5
  export function make(config) {
5
6
  if ("jsonSchema" in config) {
6
7
  return {
@@ -36,7 +37,7 @@ export function make(config) {
36
37
  _definition: new ToolDefinition({
37
38
  name: "",
38
39
  description: config.description,
39
- inputSchema: toJsonSchema(config.parameters),
40
+ inputSchema: emptyInputJsonSchema(config.parameters) ?? toJsonSchema(config.parameters),
40
41
  outputSchema: toJsonSchema(config.success),
41
42
  }),
42
43
  };
@@ -55,8 +56,26 @@ export const toDefinitions = (tools) => Object.entries(tools).map(([name, item])
55
56
  inputSchema: item._definition.inputSchema,
56
57
  outputSchema: item._definition.outputSchema,
57
58
  }));
59
+ /**
60
+ * Describes a no-argument Effect tool input as an empty object, or returns
61
+ * `undefined` when the input takes arguments.
62
+ *
63
+ * Effect emits `{ not: { type: "null" } }` only for an encoded struct with no
64
+ * properties or index signatures, but providers require tool parameters to
65
+ * describe an object. Annotations are kept; any other keyword, such as a
66
+ * check's `minProperties`, leaves the schema as Effect emitted it.
67
+ */
68
+ export const emptyInputJsonSchema = (schema) => {
69
+ const { not, ...rest } = Schema.toJsonSchemaDocument(schema, { referencePolicy: () => undefined }).schema;
70
+ if (!isRecord(not) || not.type !== "null" || Object.keys(not).length !== 1)
71
+ return undefined;
72
+ if (!Object.keys(rest).every((key) => annotationKeywords.has(key)))
73
+ return undefined;
74
+ return { ...rest, type: "object", properties: {}, additionalProperties: false };
75
+ };
76
+ const annotationKeywords = new Set(["title", "description", "default", "examples", "readOnly", "writeOnly"]);
58
77
  const toJsonSchema = (schema) => {
59
- const document = Schema.toJsonSchemaDocument(schema);
78
+ const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" });
60
79
  if (Object.keys(document.definitions).length === 0)
61
80
  return document.schema;
62
81
  return { ...document.schema, $defs: document.definitions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "2.0.25",
3
+ "version": "2.0.27",
4
4
  "name": "@opencode/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@clack/prompts": "1.0.0-alpha.1",
36
- "@effect/platform-node": "4.0.0-rc.112",
37
- "@opencode/http-recorder": "2.0.25",
36
+ "@effect/platform-node": "4.0.1",
37
+ "@opencode/http-recorder": "2.0.27",
38
38
  "@tsconfig/bun": "1.0.9",
39
39
  "@types/bun": "1.4.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -44,9 +44,9 @@
44
44
  "@aws-sdk/credential-providers": "3.1057.0",
45
45
  "@smithy/eventstream-codec": "4.2.14",
46
46
  "@smithy/util-utf8": "4.2.2",
47
- "@opencode/schema": "2.0.25",
47
+ "@opencode/schema": "2.0.27",
48
48
  "aws4fetch": "1.0.20",
49
- "effect": "4.0.0-rc.112",
49
+ "effect": "4.0.1",
50
50
  "google-auth-library": "10.5.0"
51
51
  }
52
52
  }