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).
- Removed @effect/language-service from both packages/core and packages/opencode tsconfig files and dependencies
- Wrapped mergeDeep calls in config loading and LLM streaming to avoid expensive remeda conditional merge type instantiations in hot paths
- Narrowed Drizzle migrate() overload signature to avoid expensive variance checks during database initialization
These changes reduce TypeScript type-checking overhead and improve startup and runtime performance for config loading, LLM streaming, and database migrations.
The cross-spawn-spawner module has been moved from src/effect/ to src/
to simplify the core package structure. The src/types.d.ts file which
contained unused type declarations has also been removed. All imports
throughout the codebase have been updated to reflect the new location.
This change reduces the package's internal complexity by flattening the
module hierarchy and removing dead code, making future maintenance easier.
Moved the cross-spawn-spawner module from packages/opencode to packages/core
to enable code sharing across the monorepo. This consolidates the process
spawning infrastructure into the core package so other packages can use
cross-platform child process spawning without duplicating the implementation.
Updated all import statements across the codebase to reference the new
location (@opencode-ai/core/effect/cross-spawn-spawner). Removed the
local copy from the opencode package along with its tests.
Move the Global module from packages/opencode/src/global to packages/core/src/global
to provide a unified location for managing XDG directories and application paths.
This eliminates duplicate path definitions across packages and ensures consistent
access to data, config, cache, state, log, and bin directories throughout the codebase.
Moves effect logging, observability, runtime utilities, flags, installation
version info, and process utilities from opencode to core package. This
enables better code sharing across packages and establishes core as the
single source of truth for foundational utilities.
All internal imports updated to use @opencode-ai/core paths for consistency.