feat(core): add embedded v2 session runtime and tool foundation (#30632)

This commit is contained in:
Kit Langton
2026-06-03 23:02:17 -04:00
committed by GitHub
parent c35267776a
commit 76ee87ead8
215 changed files with 31344 additions and 3278 deletions
@@ -4,10 +4,10 @@ import { ProviderTransform } from "@/provider/transform"
import { errorMessage } from "@/util/error"
import { isRecord } from "@/util/record"
import { asSchema, type ModelMessage, type Tool } from "ai"
import { Effect } from "effect"
import { Cause, Effect, FiberSet, Queue } from "effect"
import * as Stream from "effect/Stream"
import { FetchHttpClient } from "effect/unstable/http"
import { tool as nativeTool, ToolFailure, type JsonSchema, type LLMEvent } from "@opencode-ai/llm"
import { LLMRequest, Tool as NativeTool, ToolFailure, ToolRuntime, toDefinitions, type JsonSchema, type LLMEvent } from "@opencode-ai/llm"
import type { LLMClientShape } from "@opencode-ai/llm/route"
import { LLMNative } from "./native-request"
@@ -78,8 +78,8 @@ export function stream(input: StreamInput): StreamResult {
// OpenAI's official wire field names, so this is identity, not translation
// — if a field ever needs to differ between the two surfaces, the
// translation belongs here, not split across both packages.
const stream = input.llmClient.stream({
request: LLMNative.request({
const tools = nativeTools(input.tools, input)
const request = LLMNative.request({
model: input.model,
apiKey: current.apiKey,
baseURL: current.baseURL,
@@ -91,9 +91,45 @@ export function stream(input: StreamInput): StreamResult {
maxOutputTokens: input.maxOutputTokens,
providerOptions: ProviderTransform.providerOptions(input.model, input.providerOptions ?? {}),
headers: { ...providerHeaders(input.provider.options.headers), ...input.headers },
}),
tools: nativeTools(input.tools, input),
})
const stream = Stream.scoped(
Stream.unwrap(
Effect.gen(function* () {
const settlements = yield* FiberSet.make<void>()
const results = yield* Queue.unbounded<LLMEvent, Cause.Done>()
const provider = input.llmClient
.stream(
LLMRequest.update(request, {
tools: [...request.tools, ...toDefinitions(tools)],
}),
)
.pipe(
Stream.flatMap((event) =>
event.type !== "tool-call" || event.providerExecuted
? Stream.make(event)
: Stream.make(event).pipe(
Stream.concat(
Stream.fromEffectDrain(
ToolRuntime.dispatch(tools, event).pipe(
Effect.flatMap((dispatched) => Queue.offerAll(results, dispatched.events)),
Effect.catchCause((cause) => Queue.failCause(results, cause)),
Effect.asVoid,
FiberSet.run(settlements, { startImmediately: true }),
),
),
),
),
),
Stream.concat(
Stream.fromEffectDrain(
FiberSet.awaitEmpty(settlements).pipe(Effect.andThen(Queue.end(results)), Effect.asVoid),
),
),
)
return provider.pipe(Stream.concat(Stream.fromQueue(results)))
}),
),
)
return {
...current,
@@ -128,7 +164,7 @@ export function nativeTools(tools: Record<string, Tool>, input: Pick<StreamInput
name,
// Tool execution remains opencode-owned. The native runtime only adapts
// the @opencode-ai/llm tool call back into the AI SDK Tool.execute shape.
nativeTool({
NativeTool.make({
description: item.description ?? "",
jsonSchema: nativeSchema(item.inputSchema),
execute: (args: unknown, ctx) =>