fc3a1bfd34
Adds the parallel `runNative()` path inside `session/llm.ts` so a narrow slice of sessions can flow through `@opencode-ai/llm` instead of the AI SDK `streamText`. Behavior is gated and shipped off by default; only callers that opt in see any difference. The full migration plan (audit gap #4) is parallel-path-with-flag, prove parity test-by-test, flip default last. This commit is phase 1: get the wire-up in place behind a flag with one protocol so we can see whether the design holds before committing to the full migration. Wire-up summary: - New flag `OPENCODE_EXPERIMENTAL_LLM_NATIVE` (also enabled by the umbrella `OPENCODE_EXPERIMENTAL`). Off by default. - The session-LLM `live` layer now consumes `RequestExecutor.Service`, and the `defaultLayer` provides `RequestExecutor.defaultLayer` so a Node fetch HTTP client backs every native stream. - `runNative(input)` returns `Stream<Event> | undefined`. `undefined` means "fall through to AI SDK." It returns a real stream only when every gate passes: the flag is set, the caller populated `input.nativeMessages` (the bridge needs typed `MessageV2.WithParts`, not the AI SDK `messages` array), the session has zero tools (Phase 2 will lift this), and the bridge routes the model to a protocol in `NATIVE_PROTOCOLS`. - `NATIVE_PROTOCOLS` is a single-entry set today: `anthropic-messages`. Other adapters are imported and registered with the client so the Phase 2 expansion is a one-line edit, not an architecture change. - Stream wiring: client.stream(req) -> Stream.flatMap(event -> fromIterable(map.map(event))) -> Stream.concat(suspended fromIterable(map.flush())) -> Stream.provideService( RequestExecutor.Service, executor). The flush stream is built lazily with `Stream.unwrap(Effect.sync(...))` so it observes the mapper final state after every upstream event has been mapped. - The mapper (`LLMNativeEvents.mapper`) emits AI-SDK-shaped session events from `LLMEvent` so downstream consumers see one shape. What this does NOT do (deferred to later phases): - No tool support on the native path (skipped, falls through). - No parity harness yet; Phase 2 builds it. - No production traffic; flag is off by default and no production caller populates `nativeMessages`. - No reasoning/cache/multi-modal coverage. Anthropic supports reasoning and cache via existing patches, so those start working as soon as a caller routes a real session through. Verification: opencode typecheck clean, bridge tests still green (33/0/0 across llm-native.test.ts + llm-bridge.test.ts); LLM package tests green (123/0/0).