@opencode/ai 2.0.16 → 2.0.18

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.
Files changed (185) hide show
  1. package/README.md +187 -129
  2. package/dist/ai-client.d.ts +8 -0
  3. package/dist/ai-client.js +12 -0
  4. package/dist/experimental/evaluation-client.d.ts +3 -3
  5. package/dist/experimental/evaluation-client.js +1 -1
  6. package/dist/experimental/evaluation.js +1 -1
  7. package/dist/generation.d.ts +6 -10
  8. package/dist/generation.js +17 -20
  9. package/dist/image-client.d.ts +59 -24
  10. package/dist/image-client.js +16 -40
  11. package/dist/image.d.ts +15 -27
  12. package/dist/image.js +4 -15
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.js +1 -0
  15. package/dist/llm.d.ts +7 -5
  16. package/dist/llm.js +10 -4
  17. package/dist/media-client.d.ts +30 -0
  18. package/dist/media-client.js +51 -0
  19. package/dist/media-model.d.ts +9 -10
  20. package/dist/media-model.js +10 -12
  21. package/dist/media.js +2 -2
  22. package/dist/promise.d.ts +68 -30
  23. package/dist/promise.js +52 -31
  24. package/dist/protocols/alibaba-chat.js +4 -1
  25. package/dist/protocols/alibaba-messages.d.ts +1 -1
  26. package/dist/protocols/alibaba-messages.js +6 -4
  27. package/dist/protocols/anthropic-messages.d.ts +34 -34
  28. package/dist/protocols/anthropic-messages.js +14 -8
  29. package/dist/protocols/assemblyai-transcription.d.ts +2 -1
  30. package/dist/protocols/assemblyai-transcription.js +14 -16
  31. package/dist/protocols/bedrock-converse.d.ts +7 -0
  32. package/dist/protocols/bedrock-converse.js +37 -11
  33. package/dist/protocols/bfl-images.d.ts +7 -1
  34. package/dist/protocols/bfl-images.js +36 -36
  35. package/dist/protocols/cartesia-speech.d.ts +18 -2
  36. package/dist/protocols/cartesia-speech.js +10 -16
  37. package/dist/protocols/deepgram-speech.d.ts +19 -3
  38. package/dist/protocols/deepgram-speech.js +11 -12
  39. package/dist/protocols/deepgram-transcription.d.ts +2 -1
  40. package/dist/protocols/deepgram-transcription.js +9 -13
  41. package/dist/protocols/elevenlabs-speech.d.ts +19 -3
  42. package/dist/protocols/elevenlabs-speech.js +9 -13
  43. package/dist/protocols/fal-images.d.ts +2 -1
  44. package/dist/protocols/fal-images.js +30 -40
  45. package/dist/protocols/fal-video.d.ts +2 -2
  46. package/dist/protocols/fal-video.js +9 -24
  47. package/dist/protocols/gemini.d.ts +13 -13
  48. package/dist/protocols/gemini.js +16 -7
  49. package/dist/protocols/google-images.d.ts +3 -3
  50. package/dist/protocols/google-images.js +11 -21
  51. package/dist/protocols/google-speech.d.ts +16 -0
  52. package/dist/protocols/google-speech.js +20 -16
  53. package/dist/protocols/google-transcription.d.ts +2 -1
  54. package/dist/protocols/google-transcription.js +8 -20
  55. package/dist/protocols/google-video.d.ts +2 -2
  56. package/dist/protocols/google-video.js +14 -30
  57. package/dist/protocols/meta-images.d.ts +3 -4
  58. package/dist/protocols/meta-images.js +12 -21
  59. package/dist/protocols/meta-messages.d.ts +5 -5
  60. package/dist/protocols/meta-responses.js +6 -4
  61. package/dist/protocols/open-responses.d.ts +4 -3
  62. package/dist/protocols/open-responses.js +4 -8
  63. package/dist/protocols/openai-chat.js +3 -4
  64. package/dist/protocols/openai-images.d.ts +7 -5
  65. package/dist/protocols/openai-images.js +65 -66
  66. package/dist/protocols/openai-responses.d.ts +10 -10
  67. package/dist/protocols/openai-responses.js +31 -30
  68. package/dist/protocols/openai-speech.d.ts +21 -1
  69. package/dist/protocols/openai-speech.js +11 -12
  70. package/dist/protocols/openai-transcription.d.ts +4 -0
  71. package/dist/protocols/openai-transcription.js +44 -32
  72. package/dist/protocols/replicate-images.js +11 -17
  73. package/dist/protocols/runway-video.d.ts +3 -3
  74. package/dist/protocols/runway-video.js +11 -17
  75. package/dist/protocols/shared.d.ts +11 -17
  76. package/dist/protocols/shared.js +11 -40
  77. package/dist/protocols/stability-images.d.ts +2 -1
  78. package/dist/protocols/stability-images.js +24 -36
  79. package/dist/protocols/utils/fal-queue.d.ts +1 -3
  80. package/dist/protocols/utils/fal-queue.js +4 -6
  81. package/dist/protocols/utils/media-input.d.ts +15 -1
  82. package/dist/protocols/utils/media-input.js +22 -0
  83. package/dist/protocols/utils/responses-checkpoint.js +3 -7
  84. package/dist/protocols/utils/responses-compaction.d.ts +3 -1
  85. package/dist/protocols/utils/responses-compaction.js +16 -3
  86. package/dist/protocols/utils/speech-stream.d.ts +3 -3
  87. package/dist/protocols/utils/speech-stream.js +1 -4
  88. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  89. package/dist/protocols/utils/tool-schema.js +29 -9
  90. package/dist/protocols/xai-images.d.ts +4 -4
  91. package/dist/protocols/xai-images.js +14 -29
  92. package/dist/protocols/xai-responses.js +1 -1
  93. package/dist/protocols/xai-video.js +12 -18
  94. package/dist/protocols/zai-images.d.ts +2 -2
  95. package/dist/protocols/zai-images.js +11 -14
  96. package/dist/protocols/zai-messages.d.ts +1 -1
  97. package/dist/provider-error.js +7 -1
  98. package/dist/providers/alibaba.d.ts +1 -1
  99. package/dist/providers/amazon-bedrock.d.ts +2 -0
  100. package/dist/providers/amazon-bedrock.js +1 -0
  101. package/dist/providers/anthropic-compatible.d.ts +5 -5
  102. package/dist/providers/anthropic.d.ts +5 -5
  103. package/dist/providers/assemblyai.d.ts +1 -1
  104. package/dist/providers/assemblyai.js +5 -10
  105. package/dist/providers/azure.d.ts +4 -4
  106. package/dist/providers/azure.js +2 -2
  107. package/dist/providers/black-forest-labs.d.ts +1 -1
  108. package/dist/providers/black-forest-labs.js +5 -10
  109. package/dist/providers/cartesia.d.ts +1 -1
  110. package/dist/providers/cartesia.js +5 -10
  111. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -14
  112. package/dist/providers/deepgram.d.ts +1 -1
  113. package/dist/providers/deepgram.js +6 -12
  114. package/dist/providers/elevenlabs.d.ts +1 -1
  115. package/dist/providers/elevenlabs.js +5 -10
  116. package/dist/providers/fal.d.ts +1 -1
  117. package/dist/providers/fal.js +5 -12
  118. package/dist/providers/google-vertex-messages.d.ts +5 -5
  119. package/dist/providers/google-vertex.d.ts +3 -3
  120. package/dist/providers/google.d.ts +3 -3
  121. package/dist/providers/google.js +7 -12
  122. package/dist/providers/meta.d.ts +8 -8
  123. package/dist/providers/meta.js +4 -8
  124. package/dist/providers/minimax.d.ts +5 -5
  125. package/dist/providers/moonshot.d.ts +5 -5
  126. package/dist/providers/openai-options.d.ts +3 -9
  127. package/dist/providers/openai-options.js +4 -7
  128. package/dist/providers/openai.d.ts +9 -10
  129. package/dist/providers/openai.js +11 -12
  130. package/dist/providers/opencode-zen.js +1 -1
  131. package/dist/providers/openrouter.d.ts +6 -7
  132. package/dist/providers/openrouter.js +10 -5
  133. package/dist/providers/replicate.d.ts +1 -1
  134. package/dist/providers/replicate.js +5 -10
  135. package/dist/providers/runway.d.ts +1 -1
  136. package/dist/providers/runway.js +5 -10
  137. package/dist/providers/stability.d.ts +1 -1
  138. package/dist/providers/stability.js +6 -11
  139. package/dist/providers/typesafe-ai.js +1 -1
  140. package/dist/providers/vercel-ai-gateway.js +1 -1
  141. package/dist/providers/xai.js +5 -10
  142. package/dist/providers/zai-coding-plan.d.ts +1 -1
  143. package/dist/providers/zai.js +4 -8
  144. package/dist/route/auth.d.ts +1 -1
  145. package/dist/route/auth.js +14 -13
  146. package/dist/route/client.d.ts +9 -7
  147. package/dist/route/client.js +6 -8
  148. package/dist/route/endpoint.d.ts +1 -0
  149. package/dist/route/endpoint.js +2 -2
  150. package/dist/route/executor-service.d.ts +4 -2
  151. package/dist/route/executor-service.js +2 -1
  152. package/dist/route/executor.d.ts +3 -1
  153. package/dist/route/executor.js +7 -0
  154. package/dist/route/framing.d.ts +10 -1
  155. package/dist/route/framing.js +45 -5
  156. package/dist/route/index.d.ts +1 -1
  157. package/dist/route/media-protocol.d.ts +49 -35
  158. package/dist/route/media-protocol.js +77 -65
  159. package/dist/route/media.d.ts +10 -16
  160. package/dist/route/media.js +46 -62
  161. package/dist/route/protocol.d.ts +3 -1
  162. package/dist/schema/options.d.ts +6 -3
  163. package/dist/schema/options.js +7 -3
  164. package/dist/speech-client.d.ts +62 -17
  165. package/dist/speech-client.js +17 -21
  166. package/dist/speech.d.ts +178 -21
  167. package/dist/speech.js +13 -8
  168. package/dist/testing.d.ts +2 -2
  169. package/dist/transcription-client.d.ts +78 -24
  170. package/dist/transcription-client.js +9 -40
  171. package/dist/transcription.d.ts +15 -27
  172. package/dist/transcription.js +4 -10
  173. package/dist/utils/json.d.ts +4 -0
  174. package/dist/utils/json.js +4 -0
  175. package/dist/utils/media-type.d.ts +2 -1
  176. package/dist/utils/media-type.js +3 -1
  177. package/dist/video-client.d.ts +55 -24
  178. package/dist/video-client.js +16 -36
  179. package/dist/video.d.ts +18 -26
  180. package/dist/video.js +15 -16
  181. package/package.json +3 -3
  182. package/dist/protocols/utils/meta-image.d.ts +0 -2
  183. package/dist/protocols/utils/meta-image.js +0 -13
  184. package/dist/protocols/utils/openai-image.d.ts +0 -5
  185. package/dist/protocols/utils/openai-image.js +0 -18
@@ -1,17 +1,12 @@
1
1
  import { AuthOptions } from "../route/auth-options.js";
2
- import { HttpOptions, ProviderID } from "../schema/index.js";
3
- import { DEFAULT_BASE_URL, RunwayVideo } from "../protocols/runway-video.js";
2
+ import { MediaRoute } from "../route/media.js";
3
+ import { ProviderID } from "../schema/index.js";
4
+ import { RunwayVideo } from "../protocols/runway-video.js";
4
5
  export const id = ProviderID.make("runway");
5
- const baseURL = DEFAULT_BASE_URL;
6
6
  const auth = (options) => AuthOptions.bearer(options, "RUNWAYML_API_SECRET");
7
7
  export const configure = (input = {}) => {
8
- const video = (modelID) => RunwayVideo.model({
9
- id: modelID,
10
- auth: auth(input),
11
- baseURL: input.baseURL ?? baseURL,
12
- headers: input.headers,
13
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
14
- });
8
+ const media = MediaRoute.deployment(input, auth(input));
9
+ const video = (modelID) => RunwayVideo.model({ ...media, id: modelID });
15
10
  return {
16
11
  id,
17
12
  video,
@@ -1,5 +1,5 @@
1
1
  import { type ProviderAuthOption } from "../route/auth-options.js";
2
- import { HttpOptions, type ModelID } from "../schema/index.js";
2
+ import { type HttpOptions, type ModelID } from "../schema/index.js";
3
3
  export type { StabilityImageOptions, StabilityUpscaleOptions } from "../protocols/stability-images.js";
4
4
  export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
5
5
  export type Config = ProviderAuthOption<"optional"> & {
@@ -1,20 +1,15 @@
1
1
  import { AuthOptions } from "../route/auth-options.js";
2
- import { HttpOptions, ProviderID } from "../schema/index.js";
3
- import { DEFAULT_BASE_URL, StabilityImages } from "../protocols/stability-images.js";
2
+ import { MediaRoute } from "../route/media.js";
3
+ import { ProviderID } from "../schema/index.js";
4
+ import { StabilityImages } from "../protocols/stability-images.js";
4
5
  export const id = ProviderID.make("stability");
5
- const baseURL = DEFAULT_BASE_URL;
6
6
  const auth = (options) => AuthOptions.bearer(options, "STABILITY_API_KEY");
7
7
  export const configure = (input = {}) => {
8
- const deployment = {
9
- auth: auth(input),
10
- baseURL: input.baseURL ?? baseURL,
11
- headers: input.headers,
12
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
13
- };
8
+ const media = MediaRoute.deployment(input, auth(input));
14
9
  return {
15
10
  id,
16
- image: (modelID) => StabilityImages.model({ ...deployment, id: modelID }),
17
- upscale: () => StabilityImages.upscaleModel(deployment),
11
+ image: (modelID) => StabilityImages.model({ ...media, id: modelID }),
12
+ upscale: () => StabilityImages.upscaleModel(media),
18
13
  configure,
19
14
  };
20
15
  };
@@ -11,7 +11,7 @@ export const configure = (input = {}) => {
11
11
  auth: AuthOptions.bearer(input, "TYPESAFE_API_KEY"),
12
12
  baseURL: input.baseURL ?? baseURL,
13
13
  headers: input.headers,
14
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
14
+ http: HttpOptions.make(input.http),
15
15
  });
16
16
  return { id, experimental: { evaluation }, configure };
17
17
  };
@@ -26,7 +26,7 @@ export const configure = (input = {}) => {
26
26
  const evaluation = (modelID) => EvaluationModel.make({
27
27
  id: modelID,
28
28
  provider: id,
29
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
29
+ http: HttpOptions.make(input.http),
30
30
  route: {
31
31
  id: "vercel-evaluation",
32
32
  evaluate: (req, send) => Effect.gen(function* () {
@@ -1,7 +1,8 @@
1
1
  import { AuthOptions } from "../route/auth-options.js";
2
2
  import { Route } from "../route/client.js";
3
3
  import { Endpoint } from "../route/endpoint.js";
4
- import { HttpOptions, ProviderID } from "../schema/index.js";
4
+ import { MediaRoute } from "../route/media.js";
5
+ import { ProviderID } from "../schema/index.js";
5
6
  import { OpenAIChat } from "../protocols/openai-chat.js";
6
7
  import { OpenResponsesChannel } from "../protocols/open-responses-channel.js";
7
8
  import { XAIResponses } from "../protocols/xai-responses.js";
@@ -60,20 +61,14 @@ export const configure = (input = {}) => {
60
61
  const chatRoute = configuredChatRoute(input);
61
62
  const responses = (modelID) => responsesRoute.model({ id: modelID });
62
63
  const chat = (modelID) => chatRoute.model({ id: modelID });
63
- const media = (modelID) => ({
64
- id: modelID,
65
- auth: auth(input),
66
- baseURL: input.baseURL ?? baseURL,
67
- headers: input.headers,
68
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
69
- });
64
+ const media = MediaRoute.deployment(input, auth(input));
70
65
  return {
71
66
  id,
72
67
  model: responses,
73
68
  responses,
74
69
  chat,
75
- image: (modelID) => XAIImages.model(media(modelID)),
76
- video: (modelID) => XAIVideo.model(media(modelID)),
70
+ image: (modelID) => XAIImages.model({ ...media, id: modelID }),
71
+ video: (modelID) => XAIVideo.model({ ...media, id: modelID }),
77
72
  configure,
78
73
  };
79
74
  };
@@ -520,7 +520,7 @@ export declare const routes: (Route<{
520
520
  readonly thinking?: {
521
521
  readonly type: string;
522
522
  } | undefined;
523
- readonly service_tier?: "auto" | (string & {}) | "standard_only" | undefined;
523
+ readonly service_tier?: import("../schema/options.js").OpenString<"auto" | "standard_only"> | undefined;
524
524
  readonly container?: string | {
525
525
  readonly id?: string | null | undefined;
526
526
  readonly skills?: readonly {
@@ -4,7 +4,8 @@ import { OpenAIChat } from "../protocols/openai-chat.js";
4
4
  import { AuthOptions } from "../route/auth-options.js";
5
5
  import { Route } from "../route/client.js";
6
6
  import { Endpoint } from "../route/endpoint.js";
7
- import { HttpOptions, ProviderID } from "../schema/index.js";
7
+ import { MediaRoute } from "../route/media.js";
8
+ import { ProviderID } from "../schema/index.js";
8
9
  export const id = ProviderID.make("zai");
9
10
  const auth = (options) => AuthOptions.bearer(options, "ZAI_API_KEY");
10
11
  const chatRoute = Route.make({
@@ -25,13 +26,8 @@ export const configure = (input = {}) => {
25
26
  auth: auth(input),
26
27
  })
27
28
  .model({ id: modelID, compatibility: ZAIChat.compatibility });
28
- const image = (modelID) => ZAIImages.model({
29
- id: modelID,
30
- auth: auth(input),
31
- baseURL: input.baseURL,
32
- headers: input.headers,
33
- http: input.http === undefined ? undefined : HttpOptions.make(input.http),
34
- });
29
+ const media = MediaRoute.deployment(input, auth(input));
30
+ const image = (modelID) => ZAIImages.model({ ...media, id: modelID });
35
31
  return {
36
32
  id,
37
33
  model: chat,
@@ -4,7 +4,7 @@ import { AIError, type HttpOptions } from "../schema/index.js";
4
4
  export declare class MissingCredentialError extends Error {
5
5
  readonly source: string;
6
6
  readonly _tag = "MissingCredentialError";
7
- constructor(source: string);
7
+ constructor(source: string, message?: string);
8
8
  }
9
9
  export type CredentialError = MissingCredentialError | Config.ConfigError;
10
10
  export type AuthError = CredentialError | AIError;
@@ -1,11 +1,11 @@
1
- import { Config, Effect, Redacted } from "effect";
1
+ import { Config, Effect, Option, Redacted } from "effect";
2
2
  import { Headers } from "effect/unstable/http";
3
- import { AuthenticationError, InvalidRequestError, AIError } from "../schema/index.js";
3
+ import { AuthenticationError, AIError } from "../schema/index.js";
4
4
  export class MissingCredentialError extends Error {
5
5
  source;
6
6
  _tag = "MissingCredentialError";
7
- constructor(source) {
8
- super(`Missing auth credential: ${source}`);
7
+ constructor(source, message = `Missing auth credential: ${source}`) {
8
+ super(message);
9
9
  this.source = source;
10
10
  }
11
11
  }
@@ -47,7 +47,12 @@ export const value = (secret, source = "value") => credentialFromSecret(secret,
47
47
  export const optional = (secret, source = "optional value") => secret === undefined
48
48
  ? credential(Effect.fail(new MissingCredentialError(source)))
49
49
  : credentialFromSecret(secret, source);
50
- export const config = (name) => credentialFromSecret(Config.redacted(name), name);
50
+ export const config = (name) => credential(Effect.gen(function* () {
51
+ const secret = yield* Config.option(Config.redacted(name));
52
+ if (Option.isSome(secret) && Redacted.value(secret.value) !== "")
53
+ return secret.value;
54
+ return yield* Effect.fail(new MissingCredentialError(name, `${name} is not set`));
55
+ }));
51
56
  export const effect = (load) => credential(load);
52
57
  export const none = auth((input) => Effect.succeed(input.headers));
53
58
  export const headers = (input) => auth((inputAuth) => Effect.succeed(Headers.setAll(inputAuth.headers, input)));
@@ -80,14 +85,10 @@ export function scheme(name, source) {
80
85
  return render(source);
81
86
  }
82
87
  const toAIError = (error) => {
83
- if (error instanceof MissingCredentialError || error instanceof Config.ConfigError) {
84
- return new AIError({
85
- reason: error instanceof MissingCredentialError
86
- ? new AuthenticationError({ message: error.message, cause: error })
87
- : new InvalidRequestError({ message: `Failed to resolve auth config: ${error.message}`, cause: error }),
88
- });
89
- }
90
- return error;
88
+ if (error instanceof AIError)
89
+ return error;
90
+ const message = error instanceof MissingCredentialError ? error.message : `Failed to resolve auth config: ${error.message}`;
91
+ return new AIError({ reason: new AuthenticationError({ message, cause: error }) });
91
92
  };
92
93
  export const toEffect = (input) => (authInput) => input.apply(authInput).pipe(Effect.mapError(toAIError));
93
94
  export * as Auth from "./auth.js";
@@ -6,7 +6,7 @@ import { Framing } from "./framing.js";
6
6
  import { HttpTransport } from "./transport/index.js";
7
7
  import type { HttpMiddleware, Transport, TransportRuntime, WebSocketChannelExecutor } from "./transport/index.js";
8
8
  import type { Protocol } from "./protocol.js";
9
- import type { ProtocolID, ProviderOptions } from "../schema/index.js";
9
+ import type { LanguageModelSanitizerCompatibility, ProtocolID, ProviderOptions } from "../schema/index.js";
10
10
  import { AIError, CompactionResponse, CompactionCheckpointResponse, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, ProviderID } from "../schema/index.js";
11
11
  export interface RouteBody<Body> {
12
12
  /** Schema for the validated provider-native body sent as the JSON request. */
@@ -31,6 +31,7 @@ export interface Route<Body, Prepared = unknown, Compact extends CompactionOpera
31
31
  readonly defaults: RouteDefaults;
32
32
  readonly body: RouteBody<Body>;
33
33
  readonly supportsEffortUpdates?: (request: LLMRequest) => boolean;
34
+ readonly sanitizer?: LanguageModelSanitizerCompatibility;
34
35
  readonly with: {
35
36
  <Next extends CompactionOperations | undefined>(patch: RoutePatch<Body, Prepared> & {
36
37
  readonly compact: Next;
@@ -71,7 +72,6 @@ export interface RoutePatch<Body, Prepared> extends RouteDefaultsInput {
71
72
  }
72
73
  type RouteMappedLanguageModelInput = RouteLanguageModelInput | RouteRoutedLanguageModelInput;
73
74
  export declare const generationOptions: (input: GenerationOptions.Input | undefined) => GenerationOptions | undefined;
74
- export declare const httpOptions: (input: HttpOptionsInput | undefined) => HttpOptions | undefined;
75
75
  export interface Interface {
76
76
  readonly compact: CompactMethod;
77
77
  readonly stream: StreamMethod;
@@ -121,9 +121,11 @@ export declare function canCompact(request: LLMRequest, options?: {
121
121
  export declare function canCompact(request: LLMRequest, options: {
122
122
  readonly mechanism: "trigger";
123
123
  }): request is CheckpointRequest;
124
- declare const Service_base: Context.ServiceClass<Service, "@opencode/LLMClient", Interface>;
125
- export declare class Service extends Service_base {
124
+ declare const LLMClientService_base: Context.ServiceClass<LLMClientService, "@opencode/LLMClient", Interface>;
125
+ export declare class LLMClientService extends LLMClientService_base {
126
126
  }
127
+ export declare const Service: typeof LLMClientService;
128
+ export type Service = LLMClientService;
127
129
  export interface MakeInput<Body, Frame, Event, State> {
128
130
  readonly compact?: CompactionOperations;
129
131
  /** Route id used in diagnostics and prepared request metadata. */
@@ -423,7 +425,7 @@ export declare const streamRequest: (request: LLMRequest, options?: StreamOption
423
425
  readonly [x: string]: unknown;
424
426
  };
425
427
  } | undefined;
426
- }, AIError, Service>;
428
+ }, AIError, LLMClientService>;
427
429
  export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
428
430
  export declare const Route: {
429
431
  readonly make: typeof make;
@@ -431,8 +433,8 @@ export declare const Route: {
431
433
  export declare const LLMClient: {
432
434
  readonly canCompact: typeof canCompact;
433
435
  readonly compact: typeof compact;
434
- readonly Service: typeof Service;
435
- readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
436
+ readonly Service: typeof LLMClientService;
437
+ readonly layer: Layer.Layer<LLMClientService, never, import("./executor-service.js").RequestExecutorService>;
436
438
  readonly stream: typeof stream;
437
439
  readonly generate: typeof generate;
438
440
  };
@@ -9,6 +9,7 @@ import { applyEffortUpdates } from "../effort-updates.js";
9
9
  import { normalizeToolHistory } from "../tool-history.js";
10
10
  import { sanitizeSurrogates } from "../utils/sanitize.js";
11
11
  import * as ProviderShared from "../protocols/shared.js";
12
+ import { ToolSchemaProjection } from "../protocols/utils/tool-schema.js";
12
13
  import { AIError, CompactionResponse, CompactionCheckpointResponse, AIErrorReason, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, InvalidProviderOutputError, ProviderConfigurationError, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
13
14
  const makeRouteLanguageModel = (route, mapped) => {
14
15
  const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined);
@@ -33,7 +34,7 @@ const mergeRouteDefaults = (base, patch) => {
33
34
  headers,
34
35
  generation: mergeGenerationOptions(generationOptions(base?.generation), generationOptions(patch.generation)),
35
36
  providerOptions: mergeProviderOptions(base?.providerOptions, patch.providerOptions),
36
- http: mergeHttpOptions(base?.http, httpOptions(patch.http), headers === undefined ? undefined : new HttpOptions({ headers })),
37
+ http: mergeHttpOptions(base?.http, HttpOptions.make(patch.http), headers === undefined ? undefined : new HttpOptions({ headers })),
37
38
  };
38
39
  };
39
40
  const endpointBaseURL = (endpoint) => typeof endpoint.baseURL === "string" ? endpoint.baseURL : undefined;
@@ -44,11 +45,6 @@ const mergeHeaders = (...items) => {
44
45
  return Object.fromEntries(entries);
45
46
  };
46
47
  export const generationOptions = (input) => input === undefined ? undefined : GenerationOptions.make(input);
47
- export const httpOptions = (input) => {
48
- if (input === undefined)
49
- return input;
50
- return HttpOptions.make(input);
51
- };
52
48
  export function canCompact(request, options) {
53
49
  if (options?.mechanism === "trigger")
54
50
  return request.model.route.compact?.trigger !== undefined;
@@ -66,8 +62,9 @@ const unsupportedCompaction = (request, mechanism) => {
66
62
  message: `${request.model.provider}/${request.model.route.id} does not support ${mechanism === "trigger" ? "trigger" : "explicit"} compaction`,
67
63
  });
68
64
  };
69
- export class Service extends Context.Service()("@opencode/LLMClient") {
65
+ export class LLMClientService extends Context.Service()("@opencode/LLMClient") {
70
66
  }
67
+ export const Service = LLMClientService;
71
68
  const resolveRequestOptions = (request) => {
72
69
  const messages = normalizeToolHistory(request.messages);
73
70
  const normalized = messages === request.messages ? request : LLMRequest.update(request, { messages });
@@ -122,6 +119,7 @@ function makeFromTransport(input) {
122
119
  defaults: routeInput.defaults ?? {},
123
120
  body: protocol.body,
124
121
  supportsEffortUpdates: protocol.supportsEffortUpdates,
122
+ sanitizer: protocol.sanitizer,
125
123
  with: (patch) => {
126
124
  const { compact, id, provider, providerMetadataKey, auth, transport, endpoint, ...defaults } = patch;
127
125
  return build({
@@ -214,7 +212,7 @@ const prepareRequest = (request) => {
214
212
  const sanitized = LLMRequest.update(original, sanitizeSurrogates({ ...LLMRequest.input(original), model: undefined }));
215
213
  // Deduplicate per sibling level; a tool and a namespace may share a name.
216
214
  const dedupe = (tools) => [...new Map(tools.map((tool) => [`${tool.type}:${tool.name}`, tool])).values()].map((tool) => tool.type === "tool" ? tool : { ...tool, tools: dedupe(tool.tools) });
217
- const resolved = applyCachePolicy(applyEffortUpdates(LLMRequest.update(sanitized, { tools: dedupe(sanitized.tools) })));
215
+ const resolved = applyCachePolicy(applyEffortUpdates(LLMRequest.update(sanitized, { tools: ToolSchemaProjection.tools(dedupe(sanitized.tools), sanitized.model) })));
218
216
  const headers = resolved.model.route.headers?.({ request: resolved });
219
217
  return headers === undefined
220
218
  ? resolved
@@ -24,5 +24,6 @@ export type EndpointPatch<Body, Request = LLMRequest> = Partial<Definition<Body,
24
24
  /** Construct an `Endpoint` from a path string or path function. */
25
25
  export declare const path: <Body, Request = LLMRequest>(value: EndpointPart<Body, Request>, options?: Omit<Definition<Body, Request>, "path">) => Definition<Body, Request>;
26
26
  export declare const merge: <Body, Request = LLMRequest>(base: Definition<Body, Request>, patch: EndpointPatch<Body, Request>) => Definition<Body, Request>;
27
+ export declare const trimBaseUrl: (value: string) => string;
27
28
  export declare const render: <Body, Request = LLMRequest>(endpoint: Definition<Body, Request>, input: EndpointInput<Body, Request>) => URL;
28
29
  export * as Endpoint from "./endpoint.js";
@@ -1,4 +1,3 @@
1
- import * as ProviderShared from "../protocols/shared.js";
2
1
  /** Construct an `Endpoint` from a path string or path function. */
3
2
  export const path = (value, options = {}) => ({
4
3
  ...options,
@@ -11,9 +10,10 @@ export const merge = (base, patch) => ({
11
10
  path: patch.path ?? base.path,
12
11
  query: patch.query === undefined ? base.query : { ...base.query, ...patch.query },
13
12
  });
13
+ export const trimBaseUrl = (value) => value.replace(/\/+$/, "");
14
14
  const renderPart = (part, input) => typeof part === "function" ? part(input) : part;
15
15
  export const render = (endpoint, input) => {
16
- const url = new URL(`${ProviderShared.trimBaseUrl(endpoint.baseURL ?? "")}${renderPart(endpoint.path, input)}`);
16
+ const url = new URL(`${trimBaseUrl(endpoint.baseURL ?? "")}${renderPart(endpoint.path, input)}`);
17
17
  for (const [key, value] of Object.entries(endpoint.query ?? {}))
18
18
  url.searchParams.set(key, value);
19
19
  return url;
@@ -6,7 +6,9 @@ export interface Interface {
6
6
  }
7
7
  export type HttpHandler = (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
8
8
  export type HttpMiddleware = (request: HttpClientRequest.HttpClientRequest, handler: HttpHandler) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
9
- declare const Service_base: Context.ServiceClass<Service, "@opencode/AI/RequestExecutor", Interface>;
10
- export declare class Service extends Service_base {
9
+ declare const RequestExecutorService_base: Context.ServiceClass<RequestExecutorService, "@opencode/AI/RequestExecutor", Interface>;
10
+ export declare class RequestExecutorService extends RequestExecutorService_base {
11
11
  }
12
+ export declare const Service: typeof RequestExecutorService;
13
+ export type Service = RequestExecutorService;
12
14
  export {};
@@ -1,3 +1,4 @@
1
1
  import { Context } from "effect";
2
- export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {
2
+ export class RequestExecutorService extends Context.Service()("@opencode/AI/RequestExecutor") {
3
3
  }
4
+ export const Service = RequestExecutorService;
@@ -18,5 +18,7 @@ export declare const httpFailure: (input: {
18
18
  export declare const responseStream: (response: HttpClientResponse.HttpClientResponse) => Stream.Stream<Uint8Array, AIError>;
19
19
  export declare const stream: (executor: Interface, request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Stream.Stream<Uint8Array, AIError>;
20
20
  export declare const layer: Layer.Layer<Service, never, HttpClient.HttpClient>;
21
- export declare const fetchLayer: Layer.Layer<Service, never, never>;
21
+ export declare const fetchLayer: Layer.Layer<import("./executor-service.js").RequestExecutorService, never, never>;
22
+ /** Run `fn` on every request: it sees the raw response before status classification, inside middleware already on `executor`, and outside per-call middleware. */
23
+ export declare const middleware: (fn: HttpMiddleware, executor?: Layer.Layer<Service>) => Layer.Layer<Service>;
22
24
  export * as RequestExecutor from "./executor.js";
@@ -180,4 +180,11 @@ export const layer = Layer.effect(Service, Effect.gen(function* () {
180
180
  });
181
181
  }));
182
182
  export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer));
183
+ /** Run `fn` on every request: it sees the raw response before status classification, inside middleware already on `executor`, and outside per-call middleware. */
184
+ export const middleware = (fn, executor = fetchLayer) => Layer.effect(Service, Effect.gen(function* () {
185
+ const inner = yield* Service;
186
+ return Service.of({
187
+ execute: (request, next) => inner.execute(request, next === undefined ? fn : (input, handler) => fn(input, (forwarded) => next(forwarded, handler))),
188
+ });
189
+ })).pipe(Layer.provide(executor));
183
190
  export * as RequestExecutor from "./executor.js";
@@ -1,5 +1,5 @@
1
1
  import { Stream } from "effect";
2
- import type { AIError } from "../schema/index.js";
2
+ import { AIError } from "../schema/index.js";
3
3
  /**
4
4
  * Decode a streaming HTTP response body into provider-protocol frames.
5
5
  *
@@ -22,6 +22,15 @@ export interface Definition<Frame> {
22
22
  /** Original wire representation when framing transforms the provider payload. */
23
23
  readonly body?: (frame: Frame) => string | undefined;
24
24
  }
25
+ /**
26
+ * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
27
+ * decoder, optionally filters named events, and drops empty events and known
28
+ * keepalives that proxies send as data. `[DONE]` is dropped by default or
29
+ * retained for protocols that use it as their stream boundary. Retry control events are ignored without
30
+ * interrupting the stream. Decoder failures become provider output errors so
31
+ * the public error channel stays `AIError`.
32
+ */
33
+ export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>, events?: ReadonlySet<string>, includeDone?: boolean) => Stream.Stream<string, AIError>;
25
34
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
26
35
  export declare const sse: Definition<string>;
27
36
  /** Server-Sent Events framing that retains the conventional `[DONE]` sentinel. */
@@ -1,16 +1,56 @@
1
- import { Stream } from "effect";
2
- import * as ProviderShared from "../protocols/shared.js";
1
+ import { Effect, Stream } from "effect";
2
+ import { makeParser } from "effect/unstable/encoding/Sse";
3
+ import { AIError, InvalidProviderOutputError } from "../schema/index.js";
4
+ /**
5
+ * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
6
+ * decoder, optionally filters named events, and drops empty events and known
7
+ * keepalives that proxies send as data. `[DONE]` is dropped by default or
8
+ * retained for protocols that use it as their stream boundary. Retry control events are ignored without
9
+ * interrupting the stream. Decoder failures become provider output errors so
10
+ * the public error channel stays `AIError`.
11
+ */
12
+ export const sseFraming = (bytes, events, includeDone = false) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
13
+ const output = [];
14
+ return {
15
+ output,
16
+ parser: makeParser((event) => {
17
+ if (event._tag === "Event")
18
+ output.push(event);
19
+ }),
20
+ };
21
+ }, (state, chunk) => Effect.gen(function* () {
22
+ const error = state.parser.feed(chunk);
23
+ if (error)
24
+ return yield* new AIError({
25
+ reason: new InvalidProviderOutputError({
26
+ route: "sse",
27
+ message: error.message,
28
+ body: chunk,
29
+ cause: error,
30
+ }),
31
+ });
32
+ return [state, state.output.splice(0)];
33
+ })), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
34
+ event.data.length > 0 &&
35
+ // Some OpenAI-compatible proxies serialize an empty flush as a bare
36
+ // `data: null`, between events or after `[DONE]`. No protocol has a
37
+ // null event, so it carries nothing and must not abort the stream.
38
+ event.data !== "null" &&
39
+ // Vertex AI partner models (e.g. `xai/grok-4.6`) send their SSE
40
+ // keepalive comment as `data: : keepalive` while reasoning.
41
+ event.data !== ": keepalive" &&
42
+ (event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
3
43
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
4
- export const sse = { id: "sse", frame: ProviderShared.sseFraming };
44
+ export const sse = { id: "sse", frame: sseFraming };
5
45
  /** Server-Sent Events framing that retains the conventional `[DONE]` sentinel. */
6
46
  export const sseWithDone = {
7
47
  id: "sse",
8
- frame: (bytes) => ProviderShared.sseFraming(bytes, undefined, true),
48
+ frame: (bytes) => sseFraming(bytes, undefined, true),
9
49
  };
10
50
  /** SSE framing restricted to protocol-recognized event names. */
11
51
  export const sseEvents = (events) => ({
12
52
  id: "sse",
13
- frame: (bytes) => ProviderShared.sseFraming(bytes, events),
53
+ frame: (bytes) => sseFraming(bytes, events),
14
54
  });
15
55
  export const lines = {
16
56
  id: "lines",
@@ -1,5 +1,5 @@
1
1
  export { Route, LLMClient } from "./client.js";
2
- export type { Route as RouteShape, RouteLanguageModelInput, RouteRoutedLanguageModelInput, RouteDefaults, RouteDefaultsInput, AnyRoute, Interface as LLMClientShape, Service as LLMClientService, StreamOptions, CompactMethod, CompactionOperations, CompactionRequest, CheckpointRequest, EndpointCompactOptions, TriggerCompactOptions, } from "./client.js";
2
+ export type { Route as RouteShape, RouteLanguageModelInput, RouteRoutedLanguageModelInput, RouteDefaults, RouteDefaultsInput, AnyRoute, Interface as LLMClientShape, LLMClientService, StreamOptions, CompactMethod, CompactionOperations, CompactionRequest, CheckpointRequest, EndpointCompactOptions, TriggerCompactOptions, } from "./client.js";
3
3
  export * from "./executor.js";
4
4
  export { Auth } from "./auth.js";
5
5
  export { AuthOptions } from "./auth-options.js";