@opencode-ai/ai 0.0.0-bootstrap.0 → 0.0.0-next-15617
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.
- package/README.md +176 -1
- package/dist/cache-policy.d.ts +2 -0
- package/dist/cache-policy.js +108 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/llm.d.ts +223 -0
- package/dist/llm.js +81 -0
- package/dist/protocols/anthropic-messages.d.ts +503 -0
- package/dist/protocols/anthropic-messages.js +745 -0
- package/dist/protocols/bedrock-converse.d.ts +476 -0
- package/dist/protocols/bedrock-converse.js +526 -0
- package/dist/protocols/bedrock-event-stream.d.ts +9 -0
- package/dist/protocols/bedrock-event-stream.js +64 -0
- package/dist/protocols/gemini.d.ts +220 -0
- package/dist/protocols/gemini.js +420 -0
- package/dist/protocols/google-vertex-anthropic.d.ts +494 -0
- package/dist/protocols/google-vertex-anthropic.js +33 -0
- package/dist/protocols/google-vertex-gemini.d.ts +59 -0
- package/dist/protocols/google-vertex-gemini.js +18 -0
- package/dist/protocols/index.d.ts +9 -0
- package/dist/protocols/index.js +9 -0
- package/dist/protocols/openai-chat.d.ts +391 -0
- package/dist/protocols/openai-chat.js +423 -0
- package/dist/protocols/openai-compatible-chat.d.ts +73 -0
- package/dist/protocols/openai-compatible-chat.js +20 -0
- package/dist/protocols/openai-compatible-responses.d.ts +85 -0
- package/dist/protocols/openai-compatible-responses.js +18 -0
- package/dist/protocols/openai-responses.d.ts +613 -0
- package/dist/protocols/openai-responses.js +828 -0
- package/dist/protocols/shared.d.ts +192 -0
- package/dist/protocols/shared.js +236 -0
- package/dist/protocols/utils/bedrock-auth.d.ts +18 -0
- package/dist/protocols/utils/bedrock-auth.js +41 -0
- package/dist/protocols/utils/bedrock-cache.d.ts +15 -0
- package/dist/protocols/utils/bedrock-cache.js +29 -0
- package/dist/protocols/utils/bedrock-media.d.ts +49 -0
- package/dist/protocols/utils/bedrock-media.js +68 -0
- package/dist/protocols/utils/cache.d.ts +6 -0
- package/dist/protocols/utils/cache.js +8 -0
- package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
- package/dist/protocols/utils/gemini-tool-schema.js +83 -0
- package/dist/protocols/utils/lifecycle.d.ts +19 -0
- package/dist/protocols/utils/lifecycle.js +65 -0
- package/dist/protocols/utils/openai-options.d.ts +22 -0
- package/dist/protocols/utils/openai-options.js +64 -0
- package/dist/protocols/utils/tool-schema.d.ts +7 -0
- package/dist/protocols/utils/tool-schema.js +79 -0
- package/dist/protocols/utils/tool-stream.d.ts +155 -0
- package/dist/protocols/utils/tool-stream.js +136 -0
- package/dist/protocols.d.ts +1 -0
- package/dist/protocols.js +1 -0
- package/dist/provider-error.d.ts +13 -0
- package/dist/provider-error.js +122 -0
- package/dist/provider-package.d.ts +13 -0
- package/dist/provider-package.js +1 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +2 -0
- package/dist/providers/amazon-bedrock.d.ts +150 -0
- package/dist/providers/amazon-bedrock.js +41 -0
- package/dist/providers/anthropic-compatible.d.ts +183 -0
- package/dist/providers/anthropic-compatible.js +44 -0
- package/dist/providers/anthropic.d.ts +181 -0
- package/dist/providers/anthropic.js +39 -0
- package/dist/providers/azure/chat.d.ts +2 -0
- package/dist/providers/azure/chat.js +1 -0
- package/dist/providers/azure/responses.d.ts +2 -0
- package/dist/providers/azure/responses.js +1 -0
- package/dist/providers/azure.d.ts +183 -0
- package/dist/providers/azure.js +89 -0
- package/dist/providers/cloudflare.d.ts +232 -0
- package/dist/providers/cloudflare.js +87 -0
- package/dist/providers/github-copilot.d.ts +167 -0
- package/dist/providers/github-copilot.js +47 -0
- package/dist/providers/google-vertex/anthropic.d.ts +2 -0
- package/dist/providers/google-vertex/anthropic.js +1 -0
- package/dist/providers/google-vertex-anthropic.d.ts +181 -0
- package/dist/providers/google-vertex-anthropic.js +46 -0
- package/dist/providers/google-vertex-shared.d.ts +22 -0
- package/dist/providers/google-vertex-shared.js +61 -0
- package/dist/providers/google-vertex.d.ts +93 -0
- package/dist/providers/google-vertex.js +49 -0
- package/dist/providers/google.d.ts +85 -0
- package/dist/providers/google.js +33 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.js +15 -0
- package/dist/providers/openai/chat.d.ts +2 -0
- package/dist/providers/openai/chat.js +1 -0
- package/dist/providers/openai/responses.d.ts +2 -0
- package/dist/providers/openai/responses.js +1 -0
- package/dist/providers/openai-compatible/responses.d.ts +1 -0
- package/dist/providers/openai-compatible/responses.js +1 -0
- package/dist/providers/openai-compatible-profile.d.ts +43 -0
- package/dist/providers/openai-compatible-profile.js +12 -0
- package/dist/providers/openai-compatible-responses.d.ts +107 -0
- package/dist/providers/openai-compatible-responses.js +33 -0
- package/dist/providers/openai-compatible.d.ts +130 -0
- package/dist/providers/openai-compatible.js +55 -0
- package/dist/providers/openai-options.d.ts +30 -0
- package/dist/providers/openai-options.js +42 -0
- package/dist/providers/openai.d.ts +264 -0
- package/dist/providers/openai.js +62 -0
- package/dist/providers/openrouter.d.ts +321 -0
- package/dist/providers/openrouter.js +65 -0
- package/dist/providers/xai.d.ts +169 -0
- package/dist/providers/xai.js +43 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.js +1 -0
- package/dist/route/auth-options.d.ts +34 -0
- package/dist/route/auth-options.js +14 -0
- package/dist/route/auth.d.ts +49 -0
- package/dist/route/auth.js +89 -0
- package/dist/route/client.d.ts +330 -0
- package/dist/route/client.js +211 -0
- package/dist/route/endpoint.d.ts +28 -0
- package/dist/route/endpoint.js +21 -0
- package/dist/route/executor.d.ts +12 -0
- package/dist/route/executor.js +229 -0
- package/dist/route/framing.d.ts +23 -0
- package/dist/route/framing.js +4 -0
- package/dist/route/index.d.ts +16 -0
- package/dist/route/index.js +9 -0
- package/dist/route/protocol.d.ts +76 -0
- package/dist/route/protocol.js +17 -0
- package/dist/route/transport/http.d.ts +33 -0
- package/dist/route/transport/http.js +98 -0
- package/dist/route/transport/index.d.ts +27 -0
- package/dist/route/transport/index.js +4 -0
- package/dist/route/transport/websocket.d.ts +56 -0
- package/dist/route/transport/websocket.js +181 -0
- package/dist/route.d.ts +1 -0
- package/dist/route.js +1 -0
- package/dist/schema/errors.d.ts +153 -0
- package/dist/schema/errors.js +150 -0
- package/dist/schema/events.d.ts +4292 -0
- package/dist/schema/events.js +482 -0
- package/dist/schema/ids.d.ts +30 -0
- package/dist/schema/ids.js +18 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/messages.d.ts +385 -0
- package/dist/schema/messages.js +246 -0
- package/dist/schema/options.d.ts +143 -0
- package/dist/schema/options.js +195 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.js +35 -0
- package/dist/tool.d.ts +134 -0
- package/dist/tool.js +66 -0
- package/dist/utils/record.d.ts +2 -0
- package/dist/utils/record.js +2 -0
- package/package.json +36 -9
- package/index.js +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,178 @@
|
|
|
1
1
|
# @opencode-ai/ai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Schema-first LLM core for opencode. One typed request, response, event, and tool language; provider quirks live in adapters, not in calling code.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Effect } from "effect"
|
|
7
|
+
import { LLM, LLMClient } from "@opencode-ai/ai"
|
|
8
|
+
import { OpenAI } from "@opencode-ai/ai/providers"
|
|
9
|
+
|
|
10
|
+
const model = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
|
|
11
|
+
|
|
12
|
+
const request = LLM.request({
|
|
13
|
+
model,
|
|
14
|
+
system: "You are concise.",
|
|
15
|
+
prompt: "Say hello in one short sentence.",
|
|
16
|
+
generation: { maxTokens: 40 },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const program = Effect.gen(function* () {
|
|
20
|
+
const response = yield* LLMClient.generate(request)
|
|
21
|
+
console.log(response.text)
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
|
26
|
+
|
|
27
|
+
## Public API
|
|
28
|
+
|
|
29
|
+
- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
|
|
30
|
+
- **`LLM.generate` / `LLM.stream`** — re-exported from `LLMClient` for one-import use.
|
|
31
|
+
- **`Message.user(...)` / `Message.assistant(...)` / `Message.tool(...)`** — message constructors from the canonical schema model.
|
|
32
|
+
- **`Model.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
|
|
33
|
+
- **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing.
|
|
34
|
+
- **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
|
|
35
|
+
|
|
36
|
+
## Caching
|
|
37
|
+
|
|
38
|
+
Prompt caching is **on by default**. Every `LLMRequest` resolves to `cache: "auto"` unless the caller opts out with `cache: "none"`. Each protocol translates `CacheHint`s to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI and Gemini do implicit caching server-side and don't need inline markers — auto is a no-op there).
|
|
39
|
+
|
|
40
|
+
### Auto placement
|
|
41
|
+
|
|
42
|
+
`"auto"` places three breakpoints — last tool definition, last system part, latest user message. The last-user-message boundary is the load-bearing detail: in a tool-use loop, a single user turn expands into many assistant/tool round-trips, all sharing that prefix. Caching at that boundary lets every intra-turn API call hit.
|
|
43
|
+
|
|
44
|
+
The math justifies the default: Anthropic's 5-minute cache write is 1.25× base, read is 0.1×, so a single reuse within 5 minutes already wins. One-shot completions below the per-model minimum-cacheable-token threshold silently no-op on the wire, so the worst case is harmless.
|
|
45
|
+
|
|
46
|
+
### Opting out
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
LLM.request({
|
|
50
|
+
model,
|
|
51
|
+
system,
|
|
52
|
+
prompt: "one-off question",
|
|
53
|
+
cache: "none",
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Granular policy
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
cache: {
|
|
61
|
+
tools?: boolean,
|
|
62
|
+
system?: boolean,
|
|
63
|
+
messages?: "latest-user-message" | "latest-assistant" | { tail: number },
|
|
64
|
+
ttlSeconds?: number, // ≥ 3600 → 1h on Anthropic/Bedrock; else 5m
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Manual hints
|
|
69
|
+
|
|
70
|
+
Inline `CacheHint` on any text / system / tool / tool-result part overrides automatic placement. The auto policy preserves manual hints; it only fills gaps.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
LLM.request({
|
|
74
|
+
model,
|
|
75
|
+
system: [
|
|
76
|
+
{ type: "text", text: "stable system prompt", cache: { type: "ephemeral" } },
|
|
77
|
+
],
|
|
78
|
+
...
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Provider behavior table
|
|
83
|
+
|
|
84
|
+
| Protocol | `cache: "auto"` |
|
|
85
|
+
| ----------------------- | ------------------------------------------------------------------------- |
|
|
86
|
+
| Anthropic Messages | emits up to 3 `cache_control` markers (4-breakpoint cap enforced) |
|
|
87
|
+
| Bedrock Converse | emits up to 3 `cachePoint` blocks (4-breakpoint cap enforced) |
|
|
88
|
+
| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) |
|
|
89
|
+
| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) |
|
|
90
|
+
|
|
91
|
+
Normalized cache usage is read back into `response.usage.cacheReadInputTokens` and `cacheWriteInputTokens` across every provider.
|
|
92
|
+
|
|
93
|
+
## Providers
|
|
94
|
+
|
|
95
|
+
Provider facades configure endpoint/auth/deployment details first, then expose model selectors that take only a model or deployment id. The selected model carries the executable route value used at runtime.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { OpenAI, CloudflareAIGateway } from "@opencode-ai/ai/providers"
|
|
99
|
+
|
|
100
|
+
const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
|
|
101
|
+
const gateway = CloudflareAIGateway.configure({
|
|
102
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
|
|
103
|
+
gatewayApiKey: process.env.CLOUDFLARE_API_TOKEN,
|
|
104
|
+
}).model("workers-ai/@cf/meta/llama-3.1-8b-instruct")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Included providers: OpenAI, Anthropic, Google (Gemini), Google Vertex Gemini and Anthropic, Amazon Bedrock, Azure OpenAI, Cloudflare AI Gateway, Cloudflare Workers AI, GitHub Copilot, OpenRouter, xAI, plus generic OpenAI-compatible Chat and Responses entrypoints and an Anthropic Messages-compatible entrypoint.
|
|
108
|
+
|
|
109
|
+
### Package-like entrypoints
|
|
110
|
+
|
|
111
|
+
Native catalog integrations load provider behavior through package-like entrypoints. These are export paths from the same `@opencode-ai/ai` npm package, not independently published packages. Each entrypoint exports the same `model(modelID, settings)` contract, and `settings` contains serializable provider configuration plus common `headers`, `body`, and `limits` overlays.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { model } from "@opencode-ai/ai/providers/openai/responses"
|
|
115
|
+
|
|
116
|
+
const selected = model("gpt-5", {
|
|
117
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
118
|
+
transport: "websocket",
|
|
119
|
+
headers: { "x-application": "opencode" },
|
|
120
|
+
limits: { context: 200_000, output: 64_000 },
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
OpenAI Chat and OpenAI Responses are separate semantic entrypoints:
|
|
125
|
+
|
|
126
|
+
- `@opencode-ai/ai/providers/openai/chat`
|
|
127
|
+
- `@opencode-ai/ai/providers/openai/responses`
|
|
128
|
+
- `@opencode-ai/ai/providers/openai-compatible/responses`
|
|
129
|
+
- `@opencode-ai/ai/providers/anthropic-compatible`
|
|
130
|
+
- `@opencode-ai/ai/providers/google-vertex`
|
|
131
|
+
- `@opencode-ai/ai/providers/google-vertex/anthropic`
|
|
132
|
+
|
|
133
|
+
Responses HTTP versus WebSocket is a scoped `transport` setting on the OpenAI Responses entrypoint, not another entrypoint. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; compatible Responses is separate at `providers/openai-compatible/responses`. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
|
|
134
|
+
|
|
135
|
+
Vertex Gemini and Vertex Anthropic are separate products with separate entrypoints. Both accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present.
|
|
136
|
+
|
|
137
|
+
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { model } from "@opencode-ai/ai/providers/google-vertex"
|
|
141
|
+
|
|
142
|
+
model("gemini-3.5-flash", { project: "my-project", location: "global" })
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { model } from "@opencode-ai/ai/providers/google-vertex/anthropic"
|
|
147
|
+
|
|
148
|
+
model("claude-sonnet-4-6", { project: "my-project", location: "global" })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Provider facades such as `OpenAI.configure(...).responses(...)` remain the direct application API. Package-like entrypoints are the self-similar loading contract used when a catalog selects behavior by export path.
|
|
152
|
+
|
|
153
|
+
Other provider exports listed above remain direct facades until they explicitly implement the package-like contract. Exporting a provider facade does not implicitly make it a catalog-loadable provider package.
|
|
154
|
+
|
|
155
|
+
## Provider options & HTTP overlays
|
|
156
|
+
|
|
157
|
+
Three escape hatches in order of stability:
|
|
158
|
+
|
|
159
|
+
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
|
|
160
|
+
2. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `promptCacheKey`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
|
|
161
|
+
3. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
|
|
162
|
+
|
|
163
|
+
Route/provider defaults are overridden by request-level values for each axis.
|
|
164
|
+
|
|
165
|
+
## Routes
|
|
166
|
+
|
|
167
|
+
Adding a new model or deployment is usually 5-15 lines using `Route.make({ protocol, endpoint, auth, framing, ... })`. The route owns endpoint/auth/framing and the protocol owns body construction plus stream parsing. Transports are reusable IO templates that receive route endpoint/auth at compile time. Capability/catalog metadata lives outside this low-level package; unsupported request shapes fail during protocol lowering. See `AGENTS.md` for the architectural detail.
|
|
168
|
+
|
|
169
|
+
## Effect
|
|
170
|
+
|
|
171
|
+
This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for runtime dispatch and import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
|
|
172
|
+
|
|
173
|
+
## See also
|
|
174
|
+
|
|
175
|
+
- `AGENTS.md` — architecture, route construction, contributor guide
|
|
176
|
+
- `STATUS.md` — native provider parity status and AI SDK migration gaps
|
|
177
|
+
- `example/tutorial.ts` — runnable end-to-end walkthrough
|
|
178
|
+
- `test/provider/*.test.ts` — fixture-first protocol tests; `*.recorded.test.ts` files cover live cassettes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Apply an `LLMRequest.cache` policy by injecting `CacheHint`s onto the parts
|
|
2
|
+
// the policy designates. Runs once at compile time, before the per-protocol
|
|
3
|
+
// body builder, so the existing inline-hint lowering path handles the rest.
|
|
4
|
+
//
|
|
5
|
+
// The default `"auto"` shape places one breakpoint at the last tool definition,
|
|
6
|
+
// one at the last system part, and one at the latest user message. This
|
|
7
|
+
// matches what production agent harnesses (LangChain's caching middleware,
|
|
8
|
+
// kern-ai's 10x cost-reduction playbook) converge on for tool-use loops: the
|
|
9
|
+
// latest user message stays put while a single turn explodes into many
|
|
10
|
+
// assistant/tool round-trips, so caching at that boundary lets every
|
|
11
|
+
// intra-turn API call hit the prefix.
|
|
12
|
+
//
|
|
13
|
+
// Manual `cache: CacheHint` placements on individual parts are preserved —
|
|
14
|
+
// this function only fills gaps the caller left empty.
|
|
15
|
+
import { CacheHint } from "./schema/options";
|
|
16
|
+
import { LLMRequest, Message, ToolDefinition } from "./schema/messages";
|
|
17
|
+
const AUTO = {
|
|
18
|
+
tools: true,
|
|
19
|
+
system: true,
|
|
20
|
+
messages: "latest-user-message",
|
|
21
|
+
};
|
|
22
|
+
const NONE = {};
|
|
23
|
+
// Resolution rules:
|
|
24
|
+
// - undefined → "auto" — caching is on by default. The math favors it:
|
|
25
|
+
// Anthropic 5m-cache write is 1.25x base, read is 0.1x,
|
|
26
|
+
// so a single reuse within 5 minutes already wins.
|
|
27
|
+
// - "auto" → tools + system + latest user msg.
|
|
28
|
+
// - "none" → no auto placement; manual `CacheHint`s still flow.
|
|
29
|
+
// - object form → exactly what the caller asked for.
|
|
30
|
+
const resolve = (policy) => {
|
|
31
|
+
if (policy === undefined || policy === "auto")
|
|
32
|
+
return AUTO;
|
|
33
|
+
if (policy === "none")
|
|
34
|
+
return NONE;
|
|
35
|
+
return policy;
|
|
36
|
+
};
|
|
37
|
+
// Protocols whose wire format ignores inline cache markers (OpenAI's implicit
|
|
38
|
+
// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
|
|
39
|
+
// whole policy pass for these — emitting hints would be harmless but pointless.
|
|
40
|
+
const RESPECTS_INLINE_HINTS = new Set(["anthropic-messages", "bedrock-converse"]);
|
|
41
|
+
const makeHint = (ttlSeconds) => ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" });
|
|
42
|
+
const markLastTool = (tools, hint) => {
|
|
43
|
+
if (tools.length === 0)
|
|
44
|
+
return tools;
|
|
45
|
+
const last = tools.length - 1;
|
|
46
|
+
if (tools[last].cache)
|
|
47
|
+
return tools;
|
|
48
|
+
return tools.map((tool, i) => (i === last ? new ToolDefinition({ ...tool, cache: hint }) : tool));
|
|
49
|
+
};
|
|
50
|
+
const markLastSystem = (system, hint) => {
|
|
51
|
+
if (system.length === 0)
|
|
52
|
+
return system;
|
|
53
|
+
const last = system.length - 1;
|
|
54
|
+
if (system[last].cache)
|
|
55
|
+
return system;
|
|
56
|
+
return system.map((part, i) => (i === last ? { ...part, cache: hint } : part));
|
|
57
|
+
};
|
|
58
|
+
const lastIndexOfRole = (messages, role) => messages.findLastIndex((m) => m.role === role);
|
|
59
|
+
// Mark the last text part of `messages[index]`. If no text part exists, mark
|
|
60
|
+
// the last content part regardless of type — that's the breakpoint position
|
|
61
|
+
// in tool-result-only messages too.
|
|
62
|
+
const markMessageAt = (messages, index, hint) => {
|
|
63
|
+
if (index < 0 || index >= messages.length)
|
|
64
|
+
return messages;
|
|
65
|
+
const target = messages[index];
|
|
66
|
+
if (target.content.length === 0)
|
|
67
|
+
return messages;
|
|
68
|
+
const lastTextIndex = target.content.findLastIndex((part) => part.type === "text");
|
|
69
|
+
const markAt = lastTextIndex >= 0 ? lastTextIndex : target.content.length - 1;
|
|
70
|
+
const existing = target.content[markAt];
|
|
71
|
+
if ("cache" in existing && existing.cache)
|
|
72
|
+
return messages;
|
|
73
|
+
const nextContent = target.content.map((part, i) => (i === markAt ? { ...part, cache: hint } : part));
|
|
74
|
+
const next = new Message({ ...target, content: nextContent });
|
|
75
|
+
// Single pass over `messages`, substituting the one updated entry. Long
|
|
76
|
+
// conversations call this on every request, so avoid `.map()` here — its
|
|
77
|
+
// closure dispatch and identity copies show up in profiling.
|
|
78
|
+
const result = messages.slice();
|
|
79
|
+
result[index] = next;
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
const markMessages = (messages, strategy, hint) => {
|
|
83
|
+
if (messages.length === 0)
|
|
84
|
+
return messages;
|
|
85
|
+
if (strategy === "latest-user-message")
|
|
86
|
+
return markMessageAt(messages, lastIndexOfRole(messages, "user"), hint);
|
|
87
|
+
if (strategy === "latest-assistant")
|
|
88
|
+
return markMessageAt(messages, lastIndexOfRole(messages, "assistant"), hint);
|
|
89
|
+
const start = Math.max(0, messages.length - strategy.tail);
|
|
90
|
+
let next = messages;
|
|
91
|
+
for (let i = start; i < messages.length; i++)
|
|
92
|
+
next = markMessageAt(next, i, hint);
|
|
93
|
+
return next;
|
|
94
|
+
};
|
|
95
|
+
export const applyCachePolicy = (request) => {
|
|
96
|
+
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id))
|
|
97
|
+
return request;
|
|
98
|
+
const policy = resolve(request.cache);
|
|
99
|
+
if (!policy.tools && !policy.system && !policy.messages)
|
|
100
|
+
return request;
|
|
101
|
+
const hint = makeHint(policy.ttlSeconds);
|
|
102
|
+
const tools = policy.tools ? markLastTool(request.tools, hint) : request.tools;
|
|
103
|
+
const system = policy.system ? markLastSystem(request.system, hint) : request.system;
|
|
104
|
+
const messages = policy.messages ? markMessages(request.messages, policy.messages, hint) : request.messages;
|
|
105
|
+
if (tools === request.tools && system === request.system && messages === request.messages)
|
|
106
|
+
return request;
|
|
107
|
+
return LLMRequest.update(request, { tools, system, messages });
|
|
108
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { LLMClient } from "./route/client";
|
|
2
|
+
export { Auth } from "./route/auth";
|
|
3
|
+
export { Provider } from "./provider";
|
|
4
|
+
export { ProviderPackage } from "./provider-package";
|
|
5
|
+
export { isContextOverflow, isContextOverflowFailure } from "./provider-error";
|
|
6
|
+
export type { RouteModelInput, RouteRoutedModelInput, Interface as LLMClientShape, Service as LLMClientService, } from "./route/client";
|
|
7
|
+
export * from "./schema";
|
|
8
|
+
export { Tool, ToolFailure, toDefinitions } from "./tool";
|
|
9
|
+
export { ToolRuntime } from "./tool-runtime";
|
|
10
|
+
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime";
|
|
11
|
+
export type { AnyExecutableTool, AnyTool, ExecutableTool, ExecutableTools, Definition as ToolShape, ToolExecute, ToolExecuteContext, ToolModelOutputInput, Tools, ToolSchema, ToolToModelOutput, } from "./tool";
|
|
12
|
+
export * as LLM from "./llm";
|
|
13
|
+
export type { Definition as ProviderDefinition, ModelFactory as ProviderModelFactory, ModelOptions as ProviderModelOptions, } from "./provider";
|
|
14
|
+
export type { Definition as ProviderPackageDefinition, Settings as ProviderPackageSettings } from "./provider-package";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { LLMClient } from "./route/client";
|
|
2
|
+
export { Auth } from "./route/auth";
|
|
3
|
+
export { Provider } from "./provider";
|
|
4
|
+
export { ProviderPackage } from "./provider-package";
|
|
5
|
+
export { isContextOverflow, isContextOverflowFailure } from "./provider-error";
|
|
6
|
+
export * from "./schema";
|
|
7
|
+
export { Tool, ToolFailure, toDefinitions } from "./tool";
|
|
8
|
+
export { ToolRuntime } from "./tool-runtime";
|
|
9
|
+
export * as LLM from "./llm";
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Effect, JsonSchema, Schema } from "effect";
|
|
2
|
+
import { GenerationOptions, HttpOptions, LLMError, LLMRequest, LLMResponse, Message, type ModelInput as SchemaModelInput, SystemPart, ToolChoice, ToolDefinition, type ContentPart, ToolResultPart } from "./schema";
|
|
3
|
+
import { type ToolSchema } from "./tool";
|
|
4
|
+
export type ModelInput = SchemaModelInput;
|
|
5
|
+
export type MessageInput = Message.Input;
|
|
6
|
+
export type ToolChoiceInput = ToolChoice.Input;
|
|
7
|
+
export type ToolChoiceMode = ToolChoice.Mode;
|
|
8
|
+
export type ToolResultInput = Parameters<typeof ToolResultPart.make>[0];
|
|
9
|
+
/** Input accepted by `LLM.request`, normalized into the canonical `LLMRequest` class. */
|
|
10
|
+
export type RequestInput = Omit<ConstructorParameters<typeof LLMRequest>[0], "system" | "messages" | "tools" | "toolChoice" | "generation" | "http" | "providerOptions"> & {
|
|
11
|
+
readonly system?: string | SystemPart | ReadonlyArray<SystemPart>;
|
|
12
|
+
readonly prompt?: string | ContentPart | ReadonlyArray<ContentPart>;
|
|
13
|
+
readonly messages?: ReadonlyArray<Message | MessageInput>;
|
|
14
|
+
readonly tools?: ReadonlyArray<ToolDefinition.Input>;
|
|
15
|
+
readonly toolChoice?: ToolChoiceInput;
|
|
16
|
+
readonly generation?: GenerationOptions.Input;
|
|
17
|
+
readonly providerOptions?: ConstructorParameters<typeof LLMRequest>[0]["providerOptions"];
|
|
18
|
+
readonly http?: HttpOptions.Input;
|
|
19
|
+
};
|
|
20
|
+
export declare const generate: typeof import("./route/client").generate;
|
|
21
|
+
export declare const stream: typeof import("./route/client").stream;
|
|
22
|
+
export declare const requestInput: (input: LLMRequest) => RequestInput;
|
|
23
|
+
export declare const request: (input: RequestInput) => LLMRequest;
|
|
24
|
+
export declare const updateRequest: (input: LLMRequest, patch: Partial<RequestInput>) => LLMRequest;
|
|
25
|
+
type GenerateObjectBase = Omit<RequestInput, "tools" | "toolChoice" | "responseFormat">;
|
|
26
|
+
export declare class GenerateObjectResponse<T> {
|
|
27
|
+
readonly object: T;
|
|
28
|
+
readonly response: LLMResponse;
|
|
29
|
+
constructor(object: T, response: LLMResponse);
|
|
30
|
+
get events(): readonly ({
|
|
31
|
+
readonly type: "step-start";
|
|
32
|
+
readonly index: number;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "text-start";
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly providerMetadata?: {
|
|
37
|
+
readonly [x: string]: {
|
|
38
|
+
readonly [x: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
} | undefined;
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: "text-delta";
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly text: string;
|
|
45
|
+
readonly providerMetadata?: {
|
|
46
|
+
readonly [x: string]: {
|
|
47
|
+
readonly [x: string]: unknown;
|
|
48
|
+
};
|
|
49
|
+
} | undefined;
|
|
50
|
+
} | {
|
|
51
|
+
readonly type: "text-end";
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly providerMetadata?: {
|
|
54
|
+
readonly [x: string]: {
|
|
55
|
+
readonly [x: string]: unknown;
|
|
56
|
+
};
|
|
57
|
+
} | undefined;
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: "reasoning-start";
|
|
60
|
+
readonly id: string;
|
|
61
|
+
readonly providerMetadata?: {
|
|
62
|
+
readonly [x: string]: {
|
|
63
|
+
readonly [x: string]: unknown;
|
|
64
|
+
};
|
|
65
|
+
} | undefined;
|
|
66
|
+
} | {
|
|
67
|
+
readonly type: "reasoning-delta";
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly text: string;
|
|
70
|
+
readonly providerMetadata?: {
|
|
71
|
+
readonly [x: string]: {
|
|
72
|
+
readonly [x: string]: unknown;
|
|
73
|
+
};
|
|
74
|
+
} | undefined;
|
|
75
|
+
} | {
|
|
76
|
+
readonly type: "reasoning-end";
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly providerMetadata?: {
|
|
79
|
+
readonly [x: string]: {
|
|
80
|
+
readonly [x: string]: unknown;
|
|
81
|
+
};
|
|
82
|
+
} | undefined;
|
|
83
|
+
} | {
|
|
84
|
+
readonly type: "tool-input-start";
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly providerMetadata?: {
|
|
88
|
+
readonly [x: string]: {
|
|
89
|
+
readonly [x: string]: unknown;
|
|
90
|
+
};
|
|
91
|
+
} | undefined;
|
|
92
|
+
} | {
|
|
93
|
+
readonly type: "tool-input-delta";
|
|
94
|
+
readonly id: string;
|
|
95
|
+
readonly name: string;
|
|
96
|
+
readonly text: string;
|
|
97
|
+
} | {
|
|
98
|
+
readonly type: "tool-input-end";
|
|
99
|
+
readonly id: string;
|
|
100
|
+
readonly name: string;
|
|
101
|
+
readonly providerMetadata?: {
|
|
102
|
+
readonly [x: string]: {
|
|
103
|
+
readonly [x: string]: unknown;
|
|
104
|
+
};
|
|
105
|
+
} | undefined;
|
|
106
|
+
} | {
|
|
107
|
+
readonly type: "tool-call";
|
|
108
|
+
readonly id: string;
|
|
109
|
+
readonly name: string;
|
|
110
|
+
readonly input: unknown;
|
|
111
|
+
readonly providerExecuted?: boolean | undefined;
|
|
112
|
+
readonly providerMetadata?: {
|
|
113
|
+
readonly [x: string]: {
|
|
114
|
+
readonly [x: string]: unknown;
|
|
115
|
+
};
|
|
116
|
+
} | undefined;
|
|
117
|
+
} | {
|
|
118
|
+
readonly type: "tool-result";
|
|
119
|
+
readonly id: string;
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly result: {
|
|
122
|
+
readonly type: "json";
|
|
123
|
+
readonly value: unknown;
|
|
124
|
+
} | {
|
|
125
|
+
readonly type: "text";
|
|
126
|
+
readonly value: unknown;
|
|
127
|
+
} | {
|
|
128
|
+
readonly type: "error";
|
|
129
|
+
readonly value: unknown;
|
|
130
|
+
} | {
|
|
131
|
+
readonly type: "content";
|
|
132
|
+
readonly value: readonly ({
|
|
133
|
+
readonly type: "text";
|
|
134
|
+
readonly text: string;
|
|
135
|
+
} | {
|
|
136
|
+
readonly type: "file";
|
|
137
|
+
readonly uri: string;
|
|
138
|
+
readonly mime: string;
|
|
139
|
+
readonly name?: string | undefined;
|
|
140
|
+
})[];
|
|
141
|
+
};
|
|
142
|
+
readonly output?: {
|
|
143
|
+
readonly structured: unknown;
|
|
144
|
+
readonly content: readonly ({
|
|
145
|
+
readonly type: "text";
|
|
146
|
+
readonly text: string;
|
|
147
|
+
} | {
|
|
148
|
+
readonly type: "file";
|
|
149
|
+
readonly uri: string;
|
|
150
|
+
readonly mime: string;
|
|
151
|
+
readonly name?: string | undefined;
|
|
152
|
+
})[];
|
|
153
|
+
} | undefined;
|
|
154
|
+
readonly providerExecuted?: boolean | undefined;
|
|
155
|
+
readonly providerMetadata?: {
|
|
156
|
+
readonly [x: string]: {
|
|
157
|
+
readonly [x: string]: unknown;
|
|
158
|
+
};
|
|
159
|
+
} | undefined;
|
|
160
|
+
} | {
|
|
161
|
+
readonly type: "tool-error";
|
|
162
|
+
readonly id: string;
|
|
163
|
+
readonly name: string;
|
|
164
|
+
readonly message: string;
|
|
165
|
+
readonly error?: unknown;
|
|
166
|
+
readonly providerMetadata?: {
|
|
167
|
+
readonly [x: string]: {
|
|
168
|
+
readonly [x: string]: unknown;
|
|
169
|
+
};
|
|
170
|
+
} | undefined;
|
|
171
|
+
} | {
|
|
172
|
+
readonly type: "step-finish";
|
|
173
|
+
readonly index: number;
|
|
174
|
+
readonly reason: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
|
|
175
|
+
readonly usage?: import("./schema").Usage | undefined;
|
|
176
|
+
readonly providerMetadata?: {
|
|
177
|
+
readonly [x: string]: {
|
|
178
|
+
readonly [x: string]: unknown;
|
|
179
|
+
};
|
|
180
|
+
} | undefined;
|
|
181
|
+
} | {
|
|
182
|
+
readonly type: "finish";
|
|
183
|
+
readonly reason: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
|
|
184
|
+
readonly usage?: import("./schema").Usage | undefined;
|
|
185
|
+
readonly providerMetadata?: {
|
|
186
|
+
readonly [x: string]: {
|
|
187
|
+
readonly [x: string]: unknown;
|
|
188
|
+
};
|
|
189
|
+
} | undefined;
|
|
190
|
+
} | {
|
|
191
|
+
readonly type: "provider-error";
|
|
192
|
+
readonly message: string;
|
|
193
|
+
readonly classification?: "context-overflow" | undefined;
|
|
194
|
+
readonly providerMetadata?: {
|
|
195
|
+
readonly [x: string]: {
|
|
196
|
+
readonly [x: string]: unknown;
|
|
197
|
+
};
|
|
198
|
+
} | undefined;
|
|
199
|
+
})[];
|
|
200
|
+
get usage(): import("./schema").Usage | undefined;
|
|
201
|
+
}
|
|
202
|
+
export interface GenerateObjectOptions<S extends ToolSchema<any>> extends GenerateObjectBase {
|
|
203
|
+
readonly schema: S;
|
|
204
|
+
}
|
|
205
|
+
export interface GenerateObjectDynamicOptions extends GenerateObjectBase {
|
|
206
|
+
/** Raw JSON Schema object describing the expected output shape. */
|
|
207
|
+
readonly jsonSchema: JsonSchema.JsonSchema;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Run a model and decode its output against `schema`. Works on every protocol
|
|
211
|
+
* because it forces a synthetic tool call internally — provider-native JSON
|
|
212
|
+
* modes are intentionally avoided so behaviour is uniform.
|
|
213
|
+
*
|
|
214
|
+
* Two input modes:
|
|
215
|
+
*
|
|
216
|
+
* 1. `schema: EffectSchema<T>` — `.object` is decoded and typed as `T`.
|
|
217
|
+
* Decode failures surface as `LLMError`.
|
|
218
|
+
* 2. `jsonSchema: JsonSchema.JsonSchema` — `.object` is `unknown`. Use when
|
|
219
|
+
* the schema is only available at runtime (MCP, plugin manifests). Caller validates.
|
|
220
|
+
*/
|
|
221
|
+
export declare function generateObject<S extends ToolSchema<any>>(options: GenerateObjectOptions<S>): Effect.Effect<GenerateObjectResponse<Schema.Schema.Type<S>>, LLMError>;
|
|
222
|
+
export declare function generateObject(options: GenerateObjectDynamicOptions): Effect.Effect<GenerateObjectResponse<unknown>, LLMError>;
|
|
223
|
+
export {};
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Effect, JsonSchema, Schema } from "effect";
|
|
2
|
+
import { LLMClient } from "./route/client";
|
|
3
|
+
import { GenerationOptions, HttpOptions, InvalidProviderOutputReason, LLMError, LLMEvent, LLMRequest, LLMResponse, Message, SystemPart, ToolChoice, ToolDefinition, ToolResultPart, } from "./schema";
|
|
4
|
+
import { make as makeTool, toDefinitions } from "./tool";
|
|
5
|
+
export const generate = LLMClient.generate;
|
|
6
|
+
export const stream = LLMClient.stream;
|
|
7
|
+
export const requestInput = (input) => ({
|
|
8
|
+
...LLMRequest.input(input),
|
|
9
|
+
});
|
|
10
|
+
export const request = (input) => {
|
|
11
|
+
const { system: requestSystem, prompt, messages, tools, toolChoice: requestToolChoice, generation: requestGeneration, providerOptions: requestProviderOptions, http: requestHttp, ...rest } = input;
|
|
12
|
+
return new LLMRequest({
|
|
13
|
+
...rest,
|
|
14
|
+
system: SystemPart.content(requestSystem),
|
|
15
|
+
messages: [...(messages?.map(Message.make) ?? []), ...(prompt === undefined ? [] : [Message.user(prompt)])],
|
|
16
|
+
tools: tools?.map(ToolDefinition.make) ?? [],
|
|
17
|
+
toolChoice: requestToolChoice ? ToolChoice.make(requestToolChoice) : undefined,
|
|
18
|
+
generation: requestGeneration === undefined ? undefined : GenerationOptions.make(requestGeneration),
|
|
19
|
+
providerOptions: requestProviderOptions,
|
|
20
|
+
http: requestHttp === undefined ? undefined : HttpOptions.make(requestHttp),
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export const updateRequest = (input, patch) => request({ ...requestInput(input), ...patch });
|
|
24
|
+
const GENERATE_OBJECT_TOOL_NAME = "generate_object";
|
|
25
|
+
const GENERATE_OBJECT_TOOL_DESCRIPTION = "Return the structured result by calling this tool.";
|
|
26
|
+
export class GenerateObjectResponse {
|
|
27
|
+
object;
|
|
28
|
+
response;
|
|
29
|
+
constructor(object, response) {
|
|
30
|
+
this.object = object;
|
|
31
|
+
this.response = response;
|
|
32
|
+
}
|
|
33
|
+
get events() {
|
|
34
|
+
return this.response.events;
|
|
35
|
+
}
|
|
36
|
+
get usage() {
|
|
37
|
+
return this.response.usage;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const runGenerateObject = Effect.fn("LLM.generateObject")(function* (options, tool) {
|
|
41
|
+
const baseRequest = request(options);
|
|
42
|
+
const generateRequest = LLMRequest.update(baseRequest, {
|
|
43
|
+
tools: toDefinitions({ [GENERATE_OBJECT_TOOL_NAME]: tool }),
|
|
44
|
+
toolChoice: ToolChoice.named(GENERATE_OBJECT_TOOL_NAME),
|
|
45
|
+
});
|
|
46
|
+
const response = yield* LLMClient.generate(generateRequest);
|
|
47
|
+
const call = response.toolCalls.find((event) => LLMEvent.is.toolCall(event) && event.name === GENERATE_OBJECT_TOOL_NAME);
|
|
48
|
+
if (!call || !LLMEvent.is.toolCall(call))
|
|
49
|
+
return yield* new LLMError({
|
|
50
|
+
module: "LLM",
|
|
51
|
+
method: "generateObject",
|
|
52
|
+
reason: new InvalidProviderOutputReason({
|
|
53
|
+
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
const object = yield* tool._decode(call.input).pipe(Effect.mapError((error) => new LLMError({
|
|
57
|
+
module: "LLM",
|
|
58
|
+
method: "generateObject",
|
|
59
|
+
reason: new InvalidProviderOutputReason({
|
|
60
|
+
message: `generateObject: tool input failed schema decode: ${error.message}`,
|
|
61
|
+
}),
|
|
62
|
+
})));
|
|
63
|
+
return new GenerateObjectResponse(object, response);
|
|
64
|
+
});
|
|
65
|
+
export function generateObject(options) {
|
|
66
|
+
if ("schema" in options) {
|
|
67
|
+
const { schema, ...rest } = options;
|
|
68
|
+
return runGenerateObject(rest, makeTool({
|
|
69
|
+
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
|
|
70
|
+
parameters: schema,
|
|
71
|
+
success: Schema.Unknown,
|
|
72
|
+
execute: () => Effect.void,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
const { jsonSchema, ...rest } = options;
|
|
76
|
+
return runGenerateObject(rest, makeTool({
|
|
77
|
+
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
|
|
78
|
+
jsonSchema,
|
|
79
|
+
execute: () => Effect.void,
|
|
80
|
+
}));
|
|
81
|
+
}
|