@opencode-ai/ai 0.0.0-beta-17492

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 (194) hide show
  1. package/README.md +392 -0
  2. package/dist/cache-policy.d.ts +2 -0
  3. package/dist/cache-policy.js +121 -0
  4. package/dist/image-client.d.ts +19 -0
  5. package/dist/image-client.js +19 -0
  6. package/dist/image.d.ts +110 -0
  7. package/dist/image.js +102 -0
  8. package/dist/index.d.ts +18 -0
  9. package/dist/index.js +12 -0
  10. package/dist/llm.d.ts +230 -0
  11. package/dist/llm.js +77 -0
  12. package/dist/protocols/anthropic-messages.d.ts +633 -0
  13. package/dist/protocols/anthropic-messages.js +860 -0
  14. package/dist/protocols/bedrock-converse.d.ts +533 -0
  15. package/dist/protocols/bedrock-converse.js +584 -0
  16. package/dist/protocols/bedrock-event-stream.d.ts +9 -0
  17. package/dist/protocols/bedrock-event-stream.js +73 -0
  18. package/dist/protocols/gemini.d.ts +300 -0
  19. package/dist/protocols/gemini.js +512 -0
  20. package/dist/protocols/google-images.d.ts +30 -0
  21. package/dist/protocols/google-images.js +191 -0
  22. package/dist/protocols/index.d.ts +10 -0
  23. package/dist/protocols/index.js +10 -0
  24. package/dist/protocols/open-responses-channel.d.ts +26 -0
  25. package/dist/protocols/open-responses-channel.js +120 -0
  26. package/dist/protocols/open-responses.d.ts +951 -0
  27. package/dist/protocols/open-responses.js +873 -0
  28. package/dist/protocols/openai-chat.d.ts +795 -0
  29. package/dist/protocols/openai-chat.js +718 -0
  30. package/dist/protocols/openai-compatible-chat.d.ts +107 -0
  31. package/dist/protocols/openai-compatible-chat.js +20 -0
  32. package/dist/protocols/openai-compatible-responses.d.ts +96 -0
  33. package/dist/protocols/openai-compatible-responses.js +17 -0
  34. package/dist/protocols/openai-images.d.ts +32 -0
  35. package/dist/protocols/openai-images.js +188 -0
  36. package/dist/protocols/openai-responses-channel.d.ts +12 -0
  37. package/dist/protocols/openai-responses-channel.js +142 -0
  38. package/dist/protocols/openai-responses.d.ts +596 -0
  39. package/dist/protocols/openai-responses.js +208 -0
  40. package/dist/protocols/shared.d.ts +194 -0
  41. package/dist/protocols/shared.js +238 -0
  42. package/dist/protocols/utils/bedrock-auth.d.ts +21 -0
  43. package/dist/protocols/utils/bedrock-auth.js +43 -0
  44. package/dist/protocols/utils/bedrock-cache.d.ts +15 -0
  45. package/dist/protocols/utils/bedrock-cache.js +29 -0
  46. package/dist/protocols/utils/bedrock-media.d.ts +49 -0
  47. package/dist/protocols/utils/bedrock-media.js +70 -0
  48. package/dist/protocols/utils/cache.d.ts +6 -0
  49. package/dist/protocols/utils/cache.js +8 -0
  50. package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
  51. package/dist/protocols/utils/gemini-tool-schema.js +103 -0
  52. package/dist/protocols/utils/image-input.d.ts +21 -0
  53. package/dist/protocols/utils/image-input.js +22 -0
  54. package/dist/protocols/utils/lifecycle.d.ts +20 -0
  55. package/dist/protocols/utils/lifecycle.js +68 -0
  56. package/dist/protocols/utils/open-responses-options.d.ts +21 -0
  57. package/dist/protocols/utils/open-responses-options.js +41 -0
  58. package/dist/protocols/utils/openai-image.d.ts +5 -0
  59. package/dist/protocols/utils/openai-image.js +18 -0
  60. package/dist/protocols/utils/openai-options.d.ts +14 -0
  61. package/dist/protocols/utils/openai-options.js +14 -0
  62. package/dist/protocols/utils/tool-schema.d.ts +8 -0
  63. package/dist/protocols/utils/tool-schema.js +81 -0
  64. package/dist/protocols/utils/tool-stream.d.ts +639 -0
  65. package/dist/protocols/utils/tool-stream.js +141 -0
  66. package/dist/protocols/xai-images.d.ts +26 -0
  67. package/dist/protocols/xai-images.js +111 -0
  68. package/dist/protocols/zai-images.d.ts +22 -0
  69. package/dist/protocols/zai-images.js +84 -0
  70. package/dist/protocols.d.ts +1 -0
  71. package/dist/protocols.js +1 -0
  72. package/dist/provider-error.d.ts +14 -0
  73. package/dist/provider-error.js +130 -0
  74. package/dist/provider-package.d.ts +15 -0
  75. package/dist/provider-package.js +1 -0
  76. package/dist/provider.d.ts +23 -0
  77. package/dist/provider.js +2 -0
  78. package/dist/providers/amazon-bedrock/mantle/chat.d.ts +2 -0
  79. package/dist/providers/amazon-bedrock/mantle/chat.js +1 -0
  80. package/dist/providers/amazon-bedrock/mantle/responses.d.ts +2 -0
  81. package/dist/providers/amazon-bedrock/mantle/responses.js +1 -0
  82. package/dist/providers/amazon-bedrock/mantle.d.ts +2 -0
  83. package/dist/providers/amazon-bedrock/mantle.js +1 -0
  84. package/dist/providers/amazon-bedrock-mantle.d.ts +247 -0
  85. package/dist/providers/amazon-bedrock-mantle.js +68 -0
  86. package/dist/providers/amazon-bedrock.d.ts +172 -0
  87. package/dist/providers/amazon-bedrock.js +41 -0
  88. package/dist/providers/anthropic-compatible.d.ts +218 -0
  89. package/dist/providers/anthropic-compatible.js +45 -0
  90. package/dist/providers/anthropic.d.ts +216 -0
  91. package/dist/providers/anthropic.js +40 -0
  92. package/dist/providers/azure/chat.d.ts +2 -0
  93. package/dist/providers/azure/chat.js +1 -0
  94. package/dist/providers/azure/responses.d.ts +2 -0
  95. package/dist/providers/azure/responses.js +1 -0
  96. package/dist/providers/azure.d.ts +248 -0
  97. package/dist/providers/azure.js +90 -0
  98. package/dist/providers/cloudflare.d.ts +338 -0
  99. package/dist/providers/cloudflare.js +87 -0
  100. package/dist/providers/google-vertex/chat.d.ts +2 -0
  101. package/dist/providers/google-vertex/chat.js +1 -0
  102. package/dist/providers/google-vertex/gemini.d.ts +2 -0
  103. package/dist/providers/google-vertex/gemini.js +1 -0
  104. package/dist/providers/google-vertex/messages.d.ts +2 -0
  105. package/dist/providers/google-vertex/messages.js +1 -0
  106. package/dist/providers/google-vertex/responses.d.ts +2 -0
  107. package/dist/providers/google-vertex/responses.js +1 -0
  108. package/dist/providers/google-vertex-chat.d.ts +131 -0
  109. package/dist/providers/google-vertex-chat.js +50 -0
  110. package/dist/providers/google-vertex-messages.d.ts +215 -0
  111. package/dist/providers/google-vertex-messages.js +75 -0
  112. package/dist/providers/google-vertex-responses.d.ts +122 -0
  113. package/dist/providers/google-vertex-responses.js +51 -0
  114. package/dist/providers/google-vertex-shared.d.ts +22 -0
  115. package/dist/providers/google-vertex-shared.js +61 -0
  116. package/dist/providers/google-vertex.d.ts +115 -0
  117. package/dist/providers/google-vertex.js +64 -0
  118. package/dist/providers/google.d.ts +112 -0
  119. package/dist/providers/google.js +43 -0
  120. package/dist/providers/index.d.ts +18 -0
  121. package/dist/providers/index.js +18 -0
  122. package/dist/providers/open-responses-options.d.ts +16 -0
  123. package/dist/providers/open-responses-options.js +1 -0
  124. package/dist/providers/openai/chat.d.ts +2 -0
  125. package/dist/providers/openai/chat.js +1 -0
  126. package/dist/providers/openai/responses.d.ts +2 -0
  127. package/dist/providers/openai/responses.js +1 -0
  128. package/dist/providers/openai-compatible/responses.d.ts +1 -0
  129. package/dist/providers/openai-compatible/responses.js +1 -0
  130. package/dist/providers/openai-compatible-profile.d.ts +43 -0
  131. package/dist/providers/openai-compatible-profile.js +12 -0
  132. package/dist/providers/openai-compatible-responses.d.ts +120 -0
  133. package/dist/providers/openai-compatible-responses.js +33 -0
  134. package/dist/providers/openai-compatible.d.ts +167 -0
  135. package/dist/providers/openai-compatible.js +55 -0
  136. package/dist/providers/openai-options.d.ts +21 -0
  137. package/dist/providers/openai-options.js +41 -0
  138. package/dist/providers/openai.d.ts +264 -0
  139. package/dist/providers/openai.js +83 -0
  140. package/dist/providers/openrouter.d.ts +537 -0
  141. package/dist/providers/openrouter.js +117 -0
  142. package/dist/providers/xai.d.ts +249 -0
  143. package/dist/providers/xai.js +79 -0
  144. package/dist/providers/zai.d.ts +24 -0
  145. package/dist/providers/zai.js +21 -0
  146. package/dist/providers.d.ts +1 -0
  147. package/dist/providers.js +1 -0
  148. package/dist/route/auth-options.d.ts +34 -0
  149. package/dist/route/auth-options.js +14 -0
  150. package/dist/route/auth.d.ts +51 -0
  151. package/dist/route/auth.js +89 -0
  152. package/dist/route/client.d.ts +348 -0
  153. package/dist/route/client.js +214 -0
  154. package/dist/route/endpoint.d.ts +28 -0
  155. package/dist/route/endpoint.js +21 -0
  156. package/dist/route/executor.d.ts +23 -0
  157. package/dist/route/executor.js +227 -0
  158. package/dist/route/framing.d.ts +23 -0
  159. package/dist/route/framing.js +4 -0
  160. package/dist/route/index.d.ts +16 -0
  161. package/dist/route/index.js +9 -0
  162. package/dist/route/protocol.d.ts +77 -0
  163. package/dist/route/protocol.js +17 -0
  164. package/dist/route/transport/http.d.ts +34 -0
  165. package/dist/route/transport/http.js +63 -0
  166. package/dist/route/transport/index.d.ts +39 -0
  167. package/dist/route/transport/index.js +4 -0
  168. package/dist/route/transport/websocket-channel.d.ts +56 -0
  169. package/dist/route/transport/websocket-channel.js +1 -0
  170. package/dist/route/transport/websocket.d.ts +55 -0
  171. package/dist/route/transport/websocket.js +326 -0
  172. package/dist/route.d.ts +1 -0
  173. package/dist/route.js +1 -0
  174. package/dist/schema/errors.d.ts +159 -0
  175. package/dist/schema/errors.js +155 -0
  176. package/dist/schema/events.d.ts +4587 -0
  177. package/dist/schema/events.js +492 -0
  178. package/dist/schema/ids.d.ts +30 -0
  179. package/dist/schema/ids.js +19 -0
  180. package/dist/schema/index.d.ts +5 -0
  181. package/dist/schema/index.js +5 -0
  182. package/dist/schema/messages.d.ts +402 -0
  183. package/dist/schema/messages.js +244 -0
  184. package/dist/schema/options.d.ts +159 -0
  185. package/dist/schema/options.js +202 -0
  186. package/dist/testing.d.ts +1493 -0
  187. package/dist/testing.js +88 -0
  188. package/dist/tool-runtime.d.ts +15 -0
  189. package/dist/tool-runtime.js +54 -0
  190. package/dist/tool.d.ts +135 -0
  191. package/dist/tool.js +66 -0
  192. package/dist/utils/record.d.ts +2 -0
  193. package/dist/utils/record.js +2 -0
  194. package/package.json +47 -0
@@ -0,0 +1,88 @@
1
+ export * as TestLLM from "./testing.js";
2
+ import { LLMClient } from "./route/client.js";
3
+ import { LLMEvent, LLMResponse, } from "./schema/index.js";
4
+ import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect";
5
+ export class Service extends Context.Service()("@opencode/ai/TestLLM") {
6
+ }
7
+ export const complete = (options, ...events) => [
8
+ LLMEvent.stepStart({ index: 0 }),
9
+ ...events,
10
+ LLMEvent.stepFinish({ index: 0, reason: options.reason, usage: options.usage }),
11
+ LLMEvent.finish({ reason: options.reason }),
12
+ ];
13
+ export const stop = (...events) => complete({ reason: { normalized: "stop" } }, ...events);
14
+ export const toolCalls = (...events) => complete({ reason: { normalized: "tool-calls" } }, ...events);
15
+ const textEvents = (value, id) => [
16
+ LLMEvent.textStart({ id }),
17
+ LLMEvent.textDelta({ id, text: value }),
18
+ LLMEvent.textEnd({ id }),
19
+ ];
20
+ export const text = (value, id) => stop(...textEvents(value, id));
21
+ export const textWithUsage = (value, id, inputTokens) => complete({ reason: { normalized: "stop" }, usage: { inputTokens, nonCachedInputTokens: inputTokens } }, ...textEvents(value, id));
22
+ export const tool = (id, name, input) => toolCalls(LLMEvent.toolCall({ id, name, input }));
23
+ export const failAfter = (error, ...events) => Stream.fromIterable(events).pipe(Stream.concat(Stream.fail(error)));
24
+ export const hangAfter = (...events) => Stream.concat(Stream.fromIterable(events), Stream.never);
25
+ const toStream = (response) => (Stream.isStream(response) ? response : Stream.fromIterable(response));
26
+ export const layer = (options = {}) => Layer.effect(Service, Effect.gen(function* () {
27
+ const requests = [];
28
+ const responses = [];
29
+ let started = Deferred.makeUnsafe();
30
+ let fallback = options.fallback;
31
+ let activeGate;
32
+ const wait = (count) => Effect.suspend(() => requests.length >= count ? Effect.void : Deferred.await(started).pipe(Effect.andThen(wait(count))));
33
+ const stream = ((request) => {
34
+ requests.push(options.transformRequest?.(request) ?? request);
35
+ const waiting = started;
36
+ started = Deferred.makeUnsafe();
37
+ Deferred.doneUnsafe(waiting, Effect.void);
38
+ const response = responses.shift() ?? fallback;
39
+ if (!response)
40
+ return Stream.die(new Error(`TestLLM has no response for request ${requests.length}`));
41
+ const streamed = toStream(response);
42
+ const gate = activeGate;
43
+ if (!gate)
44
+ return streamed;
45
+ return Stream.unwrap(Queue.offer(gate.started, undefined).pipe(Effect.andThen(gate.release.await), Effect.as(streamed)));
46
+ });
47
+ const client = LLMClient.Service.of({
48
+ stream,
49
+ generate: (request) => stream(request).pipe(Stream.runFold(LLMResponse.empty, LLMResponse.reduce), Effect.flatMap((state) => {
50
+ const response = LLMResponse.complete(state);
51
+ if (response)
52
+ return Effect.succeed(response);
53
+ return Effect.die("TestLLM response ended without a terminal finish event");
54
+ })),
55
+ });
56
+ return Service.of({
57
+ requests,
58
+ push: (...input) => Effect.sync(() => {
59
+ responses.push(...input);
60
+ }),
61
+ always: (response) => Effect.sync(() => {
62
+ fallback = response;
63
+ }),
64
+ wait,
65
+ gate: Effect.gen(function* () {
66
+ const gate = {
67
+ started: yield* Effect.acquireRelease(Queue.unbounded(), Queue.shutdown),
68
+ release: yield* Latch.make(),
69
+ };
70
+ activeGate = gate;
71
+ const release = Effect.sync(() => {
72
+ if (activeGate === gate)
73
+ activeGate = undefined;
74
+ }).pipe(Effect.andThen(gate.release.open), Effect.asVoid);
75
+ yield* Effect.addFinalizer(() => release);
76
+ return {
77
+ started: Queue.take(gate.started),
78
+ release,
79
+ };
80
+ }),
81
+ client,
82
+ });
83
+ }));
84
+ export const clientLayer = Layer.effect(LLMClient.Service, Effect.map(Service, (service) => service.client));
85
+ export const push = (...responses) => Service.use((service) => service.push(...responses));
86
+ export const always = (response) => Service.use((service) => service.always(response));
87
+ export const wait = (count) => Service.use((service) => service.wait(count));
88
+ export const gate = Service.use((service) => service.gate);
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { LLMEvent, type ToolCallPart, type ToolOutput as ToolOutputType, type ToolResultValue as ToolResultValueType } from "./schema/index.js";
3
+ import { type Tools } from "./tool.js";
4
+ export interface ToolSettlement {
5
+ readonly result: ToolResultValueType;
6
+ readonly output?: ToolOutputType;
7
+ }
8
+ export interface DispatchResult extends ToolSettlement {
9
+ readonly events: ReadonlyArray<LLMEvent>;
10
+ }
11
+ /** Execute one canonical tool call without owning provider IO or continuation. */
12
+ export declare const dispatch: (tools: Tools, call: ToolCallPart) => Effect.Effect<DispatchResult>;
13
+ export declare const ToolRuntime: {
14
+ readonly dispatch: (tools: Tools, call: ToolCallPart) => Effect.Effect<DispatchResult>;
15
+ };
@@ -0,0 +1,54 @@
1
+ import { Effect } from "effect";
2
+ import { LLMEvent, ToolFailure, ToolOutput, ToolResultValue, } from "./schema/index.js";
3
+ import {} from "./tool.js";
4
+ /** Execute one canonical tool call without owning provider IO or continuation. */
5
+ export const dispatch = (tools, call) => {
6
+ const tool = tools[call.name];
7
+ if (!tool)
8
+ return Effect.succeed(result(call, { type: "error", value: `Unknown tool: ${call.name}` }));
9
+ if (!tool.execute)
10
+ return Effect.succeed(result(call, { type: "error", value: `Tool has no execute handler: ${call.name}` }));
11
+ return decodeAndExecute(tool, call).pipe(Effect.map((value) => result(call, value)), Effect.catchTag("Tool.Error", (failure) => Effect.succeed(result(call, { type: "error", value: failure.message }, failure.error))));
12
+ };
13
+ const decodeAndExecute = (tool, call) => tool._decode(call.input).pipe(Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })), Effect.flatMap((decoded) => tool.execute(decoded, { id: call.id, name: call.name }).pipe(Effect.flatMap((value) => tool._encode(value).pipe(Effect.mapError((error) => new ToolFailure({
14
+ message: `Tool returned an invalid value for its success schema: ${error.message}`,
15
+ })))), Effect.map((encoded) => {
16
+ if (tool._legacyResult && ToolResultValue.is(encoded))
17
+ return { result: encoded, output: ToolOutput.fromResultValue(encoded) };
18
+ const output = tool._project(decoded, call.id, encoded);
19
+ const result = ToolOutput.toResultValue(output);
20
+ return result.type === "error" ? { result } : { result, output };
21
+ }))));
22
+ const result = (call, value, error) => {
23
+ const settlement = ToolResultValue.is(value) ? { result: value } : value;
24
+ return {
25
+ result: settlement.result,
26
+ output: settlement.output,
27
+ events: settlement.result.type === "error"
28
+ ? [
29
+ LLMEvent.toolError({
30
+ id: call.id,
31
+ name: call.name,
32
+ message: String(settlement.result.value),
33
+ error,
34
+ providerMetadata: call.providerMetadata,
35
+ }),
36
+ LLMEvent.toolResult({
37
+ id: call.id,
38
+ name: call.name,
39
+ result: settlement.result,
40
+ providerMetadata: call.providerMetadata,
41
+ }),
42
+ ]
43
+ : [
44
+ LLMEvent.toolResult({
45
+ id: call.id,
46
+ name: call.name,
47
+ result: settlement.result,
48
+ output: settlement.output,
49
+ providerMetadata: call.providerMetadata,
50
+ }),
51
+ ],
52
+ };
53
+ };
54
+ export const ToolRuntime = { dispatch };
package/dist/tool.d.ts ADDED
@@ -0,0 +1,135 @@
1
+ import { Effect, JsonSchema, Schema } from "effect";
2
+ import { Tool } from "@opencode-ai/schema/tool";
3
+ import type { ToolCallPart, ToolDefinition as ToolDefinitionClass, ToolOutput as ToolOutputType } from "./schema/index.js";
4
+ import { ToolFailure } from "./schema/index.js";
5
+ /**
6
+ * Schema constraint for tool parameters / success values: no decoding or
7
+ * encoding services are allowed. Tools should be self-contained — anything
8
+ * beyond pure data conversion belongs in the handler closure.
9
+ */
10
+ export type ToolSchema<T> = Schema.Codec<T, any, never, never>;
11
+ export interface ToolExecuteContext {
12
+ readonly id: ToolCallPart["id"];
13
+ readonly name: ToolCallPart["name"];
14
+ }
15
+ export type ToolExecute<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = (params: Schema.Schema.Type<Parameters>, context?: ToolExecuteContext) => Effect.Effect<Schema.Schema.Type<Success>, ToolFailure>;
16
+ export interface ToolModelOutputInput<Parameters, Output> {
17
+ readonly id: ToolCallPart["id"];
18
+ readonly parameters: Parameters;
19
+ readonly output: Output;
20
+ }
21
+ export type ToolToModelOutput<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = (input: ToolModelOutputInput<Schema.Schema.Type<Parameters>, Success["Encoded"]>) => ReadonlyArray<Tool.Content>;
22
+ /**
23
+ * A type-safe LLM tool. Each tool bundles its own description, parameter
24
+ * Schema and success Schema. The execute handler is optional: omit it when you
25
+ * only want to expose a tool schema to the model and handle tool calls outside
26
+ * this package.
27
+ *
28
+ * Errors must be expressed as `ToolFailure`. Unmapped errors and defects fail
29
+ * the stream.
30
+ *
31
+ * Internally each tool also carries memoized codecs and a precomputed
32
+ * `ToolDefinition` so callers do not rebuild them per invocation.
33
+ */
34
+ export interface Definition<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> {
35
+ readonly description: string;
36
+ readonly parameters: Parameters;
37
+ readonly success: Success;
38
+ readonly execute?: ToolExecute<Parameters, Success>;
39
+ readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
40
+ readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
41
+ /** @internal */
42
+ readonly _decode: (input: unknown) => Effect.Effect<Schema.Schema.Type<Parameters>, Schema.SchemaError>;
43
+ /** @internal */
44
+ readonly _encode: (value: Schema.Schema.Type<Success>) => Effect.Effect<unknown, Schema.SchemaError>;
45
+ /** @internal */
46
+ readonly _project: (parameters: Schema.Schema.Type<Parameters>, id: ToolCallPart["id"], output: unknown) => ToolOutputType;
47
+ /** @internal */
48
+ readonly _legacyResult: boolean;
49
+ /** @internal */
50
+ readonly _definition: ToolDefinitionClass;
51
+ }
52
+ export type AnyTool = Definition<any, any>;
53
+ export type ExecutableTool<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = Definition<Parameters, Success> & {
54
+ readonly execute: ToolExecute<Parameters, Success>;
55
+ };
56
+ export type AnyExecutableTool = ExecutableTool<any, any>;
57
+ export type ExecutableTools = Record<string, AnyExecutableTool>;
58
+ /**
59
+ * Constructs a tool. Two input modes:
60
+ *
61
+ * 1. **Typed** — pass Effect `parameters` and `success` Schemas; inputs and
62
+ * outputs are statically typed and decoded/encoded automatically.
63
+ *
64
+ * ```ts
65
+ * Tool.make({
66
+ * description: "Get current weather",
67
+ * parameters: Schema.Struct({ city: Schema.String }),
68
+ * success: Schema.Struct({ temperature: Schema.Number }),
69
+ * execute: ({ city }) => Effect.succeed({ temperature: 22 }),
70
+ * })
71
+ * ```
72
+ *
73
+ * 2. **Dynamic** — pass raw JSON Schema as `jsonSchema`. Use this when the
74
+ * schema comes from an external source (MCP server, plugin manifest,
75
+ * dynamic config) and is not known at compile time. Inputs are typed as
76
+ * `unknown`; the handler is responsible for any validation it needs.
77
+ *
78
+ * ```ts
79
+ * Tool.make({
80
+ * description: "Look something up",
81
+ * jsonSchema: { type: "object", properties: { ... } },
82
+ * execute: (params) => Effect.succeed(...),
83
+ * })
84
+ * ```
85
+ *
86
+ * In both modes the produced tool flows through `toDefinitions(...)`
87
+ * identically.
88
+ */
89
+ export declare function make<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>>(config: {
90
+ readonly description: string;
91
+ readonly parameters: Parameters;
92
+ readonly success: Success;
93
+ readonly execute: ToolExecute<Parameters, Success>;
94
+ readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
95
+ readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
96
+ }): ExecutableTool<Parameters, Success>;
97
+ export declare function make<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>>(config: {
98
+ readonly description: string;
99
+ readonly parameters: Parameters;
100
+ readonly success: Success;
101
+ readonly execute?: undefined;
102
+ readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
103
+ readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
104
+ }): Definition<Parameters, Success>;
105
+ export declare function make(config: {
106
+ readonly description: string;
107
+ readonly jsonSchema: JsonSchema.JsonSchema;
108
+ readonly outputSchema?: JsonSchema.JsonSchema;
109
+ readonly execute: (params: unknown, context?: ToolExecuteContext) => Effect.Effect<unknown, ToolFailure>;
110
+ readonly toModelOutput?: (input: ToolModelOutputInput<unknown, unknown>) => ReadonlyArray<Tool.Content>;
111
+ readonly toStructuredOutput?: (output: unknown) => unknown;
112
+ }): AnyExecutableTool;
113
+ export declare function make(config: {
114
+ readonly description: string;
115
+ readonly jsonSchema: JsonSchema.JsonSchema;
116
+ readonly outputSchema?: JsonSchema.JsonSchema;
117
+ readonly execute?: undefined;
118
+ readonly toModelOutput?: (input: ToolModelOutputInput<unknown, unknown>) => ReadonlyArray<Tool.Content>;
119
+ readonly toStructuredOutput?: (output: unknown) => unknown;
120
+ }): AnyTool;
121
+ /**
122
+ * A record of named tools. The record key becomes the tool name on the wire.
123
+ */
124
+ export type Tools = Record<string, AnyTool>;
125
+ /**
126
+ * Convert a tools record into the `ToolDefinition[]` shape that
127
+ * `LLMRequest.tools` expects.
128
+ *
129
+ * Tool names come from the record keys, so the per-tool cached
130
+ * `_definition` is rebuilt with the correct name here. The JSON Schema body
131
+ * is reused.
132
+ */
133
+ export declare const toDefinitions: (tools: Tools) => ReadonlyArray<ToolDefinitionClass>;
134
+ export { ToolFailure };
135
+ export * as Tool from "./tool.js";
package/dist/tool.js ADDED
@@ -0,0 +1,66 @@
1
+ import { Effect, JsonSchema, Schema } from "effect";
2
+ import { Tool } from "@opencode-ai/schema/tool";
3
+ import { ToolDefinition, ToolFailure, ToolOutput } from "./schema/index.js";
4
+ export function make(config) {
5
+ if ("jsonSchema" in config) {
6
+ return {
7
+ description: config.description,
8
+ parameters: Schema.Unknown,
9
+ success: Schema.Unknown,
10
+ execute: config.execute,
11
+ toModelOutput: config.toModelOutput,
12
+ toStructuredOutput: config.toStructuredOutput,
13
+ _decode: Effect.succeed,
14
+ _encode: Effect.succeed,
15
+ _project: (parameters, id, output) => project(config.toModelOutput, config.toStructuredOutput, parameters, id, output),
16
+ _legacyResult: config.toModelOutput === undefined && config.toStructuredOutput === undefined,
17
+ _definition: new ToolDefinition({
18
+ name: "",
19
+ description: config.description,
20
+ inputSchema: config.jsonSchema,
21
+ outputSchema: config.outputSchema,
22
+ }),
23
+ };
24
+ }
25
+ return {
26
+ description: config.description,
27
+ parameters: config.parameters,
28
+ success: config.success,
29
+ execute: config.execute,
30
+ toModelOutput: config.toModelOutput,
31
+ toStructuredOutput: config.toStructuredOutput,
32
+ _decode: Schema.decodeUnknownEffect(config.parameters),
33
+ _encode: Schema.encodeEffect(config.success),
34
+ _project: (parameters, id, output) => project(config.toModelOutput, config.toStructuredOutput, parameters, id, output),
35
+ _legacyResult: false,
36
+ _definition: new ToolDefinition({
37
+ name: "",
38
+ description: config.description,
39
+ inputSchema: toJsonSchema(config.parameters),
40
+ outputSchema: toJsonSchema(config.success),
41
+ }),
42
+ };
43
+ }
44
+ /**
45
+ * Convert a tools record into the `ToolDefinition[]` shape that
46
+ * `LLMRequest.tools` expects.
47
+ *
48
+ * Tool names come from the record keys, so the per-tool cached
49
+ * `_definition` is rebuilt with the correct name here. The JSON Schema body
50
+ * is reused.
51
+ */
52
+ export const toDefinitions = (tools) => Object.entries(tools).map(([name, item]) => new ToolDefinition({
53
+ name,
54
+ description: item._definition.description,
55
+ inputSchema: item._definition.inputSchema,
56
+ outputSchema: item._definition.outputSchema,
57
+ }));
58
+ const toJsonSchema = (schema) => {
59
+ const document = Schema.toJsonSchemaDocument(schema);
60
+ if (Object.keys(document.definitions).length === 0)
61
+ return document.schema;
62
+ return { ...document.schema, $defs: document.definitions };
63
+ };
64
+ const project = (toModelOutput, toStructuredOutput, parameters, id, output) => ToolOutput.make(toStructuredOutput?.(output) ?? output, toModelOutput?.({ id, parameters, output }) ?? (typeof output === "string" ? [{ type: "text", text: output }] : []));
65
+ export { ToolFailure };
66
+ export * as Tool from "./tool.js";
@@ -0,0 +1,2 @@
1
+ /** Plain-record narrowing. Excludes arrays so JSON object checks don't accept tuples as key/value bags. */
2
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
@@ -0,0 +1,2 @@
1
+ /** Plain-record narrowing. Excludes arrays so JSON object checks don't accept tuples as key/value bags. */
2
+ export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "version": "0.0.0-beta-17492",
4
+ "name": "@opencode-ai/ai",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "setup:recording-env": "bun run script/setup-recording-env.ts",
9
+ "test": "bun test --timeout 30000 --only-failures",
10
+ "typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.types.json",
11
+ "build": "tsc -p tsconfig.build.json"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
20
+ },
21
+ "./testing": {
22
+ "import": "./dist/testing.js",
23
+ "types": "./dist/testing.d.ts"
24
+ },
25
+ "./*": {
26
+ "import": "./dist/*.js",
27
+ "types": "./dist/*.d.ts"
28
+ }
29
+ },
30
+ "devDependencies": {
31
+ "@clack/prompts": "1.0.0-alpha.1",
32
+ "@effect/platform-node": "4.0.0-beta.101",
33
+ "@opencode-ai/http-recorder": "0.0.0-beta-17492",
34
+ "@tsconfig/bun": "1.0.9",
35
+ "@types/bun": "1.3.13",
36
+ "@typescript/native-preview": "7.0.0-dev.20251207.1",
37
+ "typescript": "5.8.2"
38
+ },
39
+ "dependencies": {
40
+ "@smithy/eventstream-codec": "4.2.14",
41
+ "@smithy/util-utf8": "4.2.2",
42
+ "@opencode-ai/schema": "0.0.0-beta-17492",
43
+ "aws4fetch": "1.0.20",
44
+ "effect": "4.0.0-beta.101",
45
+ "google-auth-library": "10.5.0"
46
+ }
47
+ }