chore: generate

This commit is contained in:
opencode-agent[bot]
2026-06-24 13:59:28 +00:00
parent 36501a81c3
commit 1ef0fd5d01
+57 -48
View File
@@ -176,7 +176,7 @@ const request = LLM.request({
}) })
// Current API: this performs one provider turn, despite the broad name. // Current API: this performs one provider turn, despite the broad name.
const response = yield* LLM.generate(request) const response = yield * LLM.generate(request)
// Current API: execution also needs LLMClient.layer and RequestExecutor services. // Current API: execution also needs LLMClient.layer and RequestExecutor services.
``` ```
@@ -242,14 +242,15 @@ executable tools. Call-level values override model defaults.
Provider-specific options are inferred from the concrete model: Provider-specific options are inferred from the concrete model:
```ts ```ts
yield* LLM.generate({ yield *
LLM.generate({
model: OpenAI.model("gpt-4.1-mini"), model: OpenAI.model("gpt-4.1-mini"),
prompt: "Hello", prompt: "Hello",
provider: { provider: {
store: false, store: false,
// OpenAI-specific autocomplete here; no `{ openai: ... }` nesting. // OpenAI-specific autocomplete here; no `{ openai: ... }` nesting.
}, },
}) })
``` ```
Code choosing between providers dynamically must narrow the model before using Code choosing between providers dynamically must narrow the model before using
@@ -278,12 +279,14 @@ provider.
### Inline input ### Inline input
```ts ```ts
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model, model,
system: "You are concise.", system: "You are concise.",
prompt: "Summarize this pull request.", prompt: "Summarize this pull request.",
generation: { maxTokens: 500 }, generation: { maxTokens: 500 },
}) })
``` ```
### Reusable portable request ### Reusable portable request
@@ -296,7 +299,7 @@ const request = LLM.request({
}) })
// Bind process-local execution behavior only when running. // Bind process-local execution behavior only when running.
const result = yield* LLM.generate({ model, request }) const result = yield * LLM.generate({ model, request })
``` ```
`LLM.request(...)` returns a plain immutable object. Use ordinary object spread `LLM.request(...)` returns a plain immutable object. Use ordinary object spread
@@ -362,11 +365,13 @@ const tools = {
}), }),
} }
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model, model,
prompt: "What is the weather in London?", prompt: "What is the weather in London?",
tools, tools,
}) })
// The runtime advertises definitions, dispatches calls, records results, and // The runtime advertises definitions, dispatches calls, records results, and
// continues provider turns automatically. // continues provider turns automatically.
@@ -387,15 +392,14 @@ successful result with `stopReason: "max-turns"`, not an Effect failure.
### Custom stopping ### Custom stopping
```ts ```ts
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model, model,
prompt, prompt,
tools, tools,
stopWhen: StopWhen.any( stopWhen: StopWhen.any(StopWhen.turnCount(8), StopWhen.hasToolCall("finalize")),
StopWhen.turnCount(8), })
StopWhen.hasToolCall("finalize"),
),
})
``` ```
`stopWhen` accepts one predicate. Composition is explicit through combinators `stopWhen` accepts one predicate. Composition is explicit through combinators
@@ -427,17 +431,13 @@ const request = LLM.request({
tools: Tool.toDefinitions(tools), tools: Tool.toDefinitions(tools),
}) })
const events = yield* LLM.stream(request).pipe(Stream.runCollect) const events = yield * LLM.stream(request).pipe(Stream.runCollect)
const call = Array.from(events).find(LLMEvent.is.toolCall) const call = Array.from(events).find(LLMEvent.is.toolCall)
if (call && !call.providerExecuted) { if (call && !call.providerExecuted) {
const dispatched = yield* ToolRuntime.dispatch(tools, call) const dispatched = yield * ToolRuntime.dispatch(tools, call)
const followUp = LLM.updateRequest(request, { const followUp = LLM.updateRequest(request, {
messages: [ messages: [...request.messages, Message.assistant([call]), Message.tool({ ...call, result: dispatched.result })],
...request.messages,
Message.assistant([call]),
Message.tool({ ...call, result: dispatched.result }),
],
}) })
// Caller must invoke the provider again and repeat the loop. // Caller must invoke the provider again and repeat the loop.
} }
@@ -452,7 +452,9 @@ OpenCode and other durable runtimes need to own persistence, tool settlement,
and continuation. They use the explicit turn API: and continuation. They use the explicit turn API:
```ts ```ts
const result = yield* LLM.generateTurn({ const result =
yield *
LLM.generateTurn({
model, model,
request, request,
// Definitions only. generateTurn never dispatches local handlers. // Definitions only. generateTurn never dispatches local handlers.
@@ -462,7 +464,7 @@ const result = yield* LLM.generateTurn({
parameters: WeatherInput, parameters: WeatherInput,
}), }),
}, },
}) })
// Persist the TurnResult and settle calls durably before the next turn. // Persist the TurnResult and settle calls durably before the next turn.
for (const call of result.toolCalls) { for (const call of result.toolCalls) {
@@ -494,7 +496,9 @@ const request = LLM.request({
}, },
}) })
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model, model,
request, request,
tools: { tools: {
@@ -506,7 +510,7 @@ const result = yield* LLM.generate({
formatError, formatError,
}), }),
}, },
}) })
``` ```
Definitions and handlers match by record key. Before the first provider call, Definitions and handlers match by record key. Before the first provider call,
@@ -516,13 +520,15 @@ binding. Missing or incompatible bindings fail with a typed tool-binding error.
Provider-hosted tools are distinct typed values: Provider-hosted tools are distinct typed values:
```ts ```ts
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model: OpenAI.model("gpt-4.1"), model: OpenAI.model("gpt-4.1"),
prompt: "Find today's relevant announcements.", prompt: "Find today's relevant announcements.",
tools: { tools: {
search: OpenAI.tool.webSearch({ searchContextSize: "medium" }), search: OpenAI.tool.webSearch({ searchContextSize: "medium" }),
}, },
}) })
``` ```
Hosted tools do not pretend to have local handlers, and callers do not inspect a Hosted tools do not pretend to have local handlers, and callers do not inspect a
@@ -589,11 +595,13 @@ const Weather = Schema.Struct({
highCelsius: Schema.Number, highCelsius: Schema.Number,
}) })
const result = yield* LLM.generate({ const result =
yield *
LLM.generate({
model, model,
prompt: "Give me today's weather for London.", prompt: "Give me today's weather for London.",
output: Weather, output: Weather,
}) })
// Inferred from Weather. // Inferred from Weather.
result.output.city result.output.city
@@ -612,11 +620,13 @@ Advanced callers may override the strategy when exact provider semantics matter.
```ts ```ts
// Current API is a separate operation and always forces a synthetic tool. // Current API is a separate operation and always forces a synthetic tool.
const result = yield* LLM.generateObject({ const result =
yield *
LLM.generateObject({
model, model,
prompt, prompt,
schema: Weather, schema: Weather,
}) })
``` ```
The proposal unifies generation and lets capabilities choose the strategy rather The proposal unifies generation and lets capabilities choose the strategy rather
@@ -679,11 +689,12 @@ cache boundaries where explicit caching is supported and does nothing on the wir
where providers cache implicitly. where providers cache implicitly.
```ts ```ts
yield* LLM.generate({ yield *
LLM.generate({
model, model,
prompt, prompt,
cache: "none", // Explicit opt-out. cache: "none", // Explicit opt-out.
}) })
``` ```
Granular cache policy remains available as an advanced request option. Granular cache policy remains available as an advanced request option.
@@ -705,13 +716,14 @@ silently inherit custom retry policies.
### Timeouts ### Timeouts
```ts ```ts
yield* LLM.generate({ yield *
LLM.generate({
model, model,
prompt, prompt,
timeout: "2 minutes", // Entire run, including tools. timeout: "2 minutes", // Entire run, including tools.
turnTimeout: "30 seconds", // Each provider turn. turnTimeout: "30 seconds", // Each provider turn.
tools, tools,
}) })
``` ```
Exact Duration input spelling follows Effect conventions. Individual tools may Exact Duration input spelling follows Effect conventions. Individual tools may
@@ -740,7 +752,8 @@ retry, or redirect control flow.
```ts ```ts
const model = OpenAI.model("gpt-4.1", { const model = OpenAI.model("gpt-4.1", {
hooks: { hooks: {
request: (request) => Effect.succeed({ request: (request) =>
Effect.succeed({
...request, ...request,
metadata: { ...request.metadata, tenant: "acme" }, metadata: { ...request.metadata, tenant: "acme" },
}), }),
@@ -775,7 +788,8 @@ The request customization ladder is:
5. Experimental provider-definition or protocol patching 5. Experimental provider-definition or protocol patching
```ts ```ts
yield* LLM.generate({ yield *
LLM.generate({
model, model,
prompt, prompt,
http: { http: {
@@ -783,7 +797,7 @@ yield* LLM.generate({
query: { debug: "true" }, query: { debug: "true" },
body: { newlyReleasedProviderField: true }, body: { newlyReleasedProviderField: true },
}, },
}) })
``` ```
Raw overlays are intentional last-resort support for provider features that ship Raw overlays are intentional last-resort support for provider features that ship
@@ -905,7 +919,7 @@ Schemas live in a dedicated namespace/subpath instead of flooding root exports:
```ts ```ts
import { LLMSchema } from "@opencode-ai/ai/schema" import { LLMSchema } from "@opencode-ai/ai/schema"
const request = yield* Schema.decodeUnknown(LLMSchema.Request)(input) const request = yield * Schema.decodeUnknown(LLMSchema.Request)(input)
``` ```
Schemas cover only serializable domain values: Schemas cover only serializable domain values:
@@ -928,10 +942,7 @@ Provider authoring is public but experimental.
### Declarative provider definition ### Declarative provider definition
```ts ```ts
import { import { Provider, Protocol } from "@opencode-ai/ai/provider"
Provider,
Protocol,
} from "@opencode-ai/ai/provider"
export const ExampleAI = Provider.define({ export const ExampleAI = Provider.define({
id: "example", id: "example",
@@ -996,9 +1007,7 @@ SDK integrations that motivated this package.
const PatchedResponses = OpenAIResponses.with({ const PatchedResponses = OpenAIResponses.with({
body: { body: {
fromRequest: (request) => fromRequest: (request) =>
OpenAIResponses.body.fromRequest(request).pipe( OpenAIResponses.body.fromRequest(request).pipe(Effect.map((body) => ({ ...body, custom_field: true }))),
Effect.map((body) => ({ ...body, custom_field: true })),
),
}, },
stream: { stream: {
step: patchResponsesStep, step: patchResponsesStep,
@@ -1043,7 +1052,7 @@ providers, and there is no preferred all-providers barrel.
## Defaults ## Defaults
| Concern | Default | | Concern | Default |
| --- | --- | | ---------------------------- | --------------------------------------------------- |
| `LLM.generate` semantics | Complete Model Run | | `LLM.generate` semantics | Complete Model Run |
| `LLM.generateTurn` semantics | Exactly one Provider Turn | | `LLM.generateTurn` semantics | Exactly one Provider Turn |
| Maximum turns | 20 | | Maximum turns | 20 |
@@ -1064,7 +1073,7 @@ providers, and there is no preferred all-providers barrel.
The redesign intentionally removes or changes these current concepts: The redesign intentionally removes or changes these current concepts:
| Current | Proposed | | Current | Proposed |
| --- | --- | | --------------------------------------- | ----------------------------------------------------------- |
| `@opencode-ai/llm` | `@opencode-ai/ai` | | `@opencode-ai/llm` | `@opencode-ai/ai` |
| Mandatory `LLM.request({ model, ... })` | Inline calls or model-free portable requests | | Mandatory `LLM.request({ model, ... })` | Inline calls or model-free portable requests |
| `LLM.generate` means one turn | `LLM.generate` means complete run | | `LLM.generate` means one turn | `LLM.generate` means complete run |