@opencode-ai/ai 0.0.0-beta-17492

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 (194) hide show
  1. package/README.md +392 -0
  2. package/dist/cache-policy.d.ts +2 -0
  3. package/dist/cache-policy.js +121 -0
  4. package/dist/image-client.d.ts +19 -0
  5. package/dist/image-client.js +19 -0
  6. package/dist/image.d.ts +110 -0
  7. package/dist/image.js +102 -0
  8. package/dist/index.d.ts +18 -0
  9. package/dist/index.js +12 -0
  10. package/dist/llm.d.ts +230 -0
  11. package/dist/llm.js +77 -0
  12. package/dist/protocols/anthropic-messages.d.ts +633 -0
  13. package/dist/protocols/anthropic-messages.js +860 -0
  14. package/dist/protocols/bedrock-converse.d.ts +533 -0
  15. package/dist/protocols/bedrock-converse.js +584 -0
  16. package/dist/protocols/bedrock-event-stream.d.ts +9 -0
  17. package/dist/protocols/bedrock-event-stream.js +73 -0
  18. package/dist/protocols/gemini.d.ts +300 -0
  19. package/dist/protocols/gemini.js +512 -0
  20. package/dist/protocols/google-images.d.ts +30 -0
  21. package/dist/protocols/google-images.js +191 -0
  22. package/dist/protocols/index.d.ts +10 -0
  23. package/dist/protocols/index.js +10 -0
  24. package/dist/protocols/open-responses-channel.d.ts +26 -0
  25. package/dist/protocols/open-responses-channel.js +120 -0
  26. package/dist/protocols/open-responses.d.ts +951 -0
  27. package/dist/protocols/open-responses.js +873 -0
  28. package/dist/protocols/openai-chat.d.ts +795 -0
  29. package/dist/protocols/openai-chat.js +718 -0
  30. package/dist/protocols/openai-compatible-chat.d.ts +107 -0
  31. package/dist/protocols/openai-compatible-chat.js +20 -0
  32. package/dist/protocols/openai-compatible-responses.d.ts +96 -0
  33. package/dist/protocols/openai-compatible-responses.js +17 -0
  34. package/dist/protocols/openai-images.d.ts +32 -0
  35. package/dist/protocols/openai-images.js +188 -0
  36. package/dist/protocols/openai-responses-channel.d.ts +12 -0
  37. package/dist/protocols/openai-responses-channel.js +142 -0
  38. package/dist/protocols/openai-responses.d.ts +596 -0
  39. package/dist/protocols/openai-responses.js +208 -0
  40. package/dist/protocols/shared.d.ts +194 -0
  41. package/dist/protocols/shared.js +238 -0
  42. package/dist/protocols/utils/bedrock-auth.d.ts +21 -0
  43. package/dist/protocols/utils/bedrock-auth.js +43 -0
  44. package/dist/protocols/utils/bedrock-cache.d.ts +15 -0
  45. package/dist/protocols/utils/bedrock-cache.js +29 -0
  46. package/dist/protocols/utils/bedrock-media.d.ts +49 -0
  47. package/dist/protocols/utils/bedrock-media.js +70 -0
  48. package/dist/protocols/utils/cache.d.ts +6 -0
  49. package/dist/protocols/utils/cache.js +8 -0
  50. package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
  51. package/dist/protocols/utils/gemini-tool-schema.js +103 -0
  52. package/dist/protocols/utils/image-input.d.ts +21 -0
  53. package/dist/protocols/utils/image-input.js +22 -0
  54. package/dist/protocols/utils/lifecycle.d.ts +20 -0
  55. package/dist/protocols/utils/lifecycle.js +68 -0
  56. package/dist/protocols/utils/open-responses-options.d.ts +21 -0
  57. package/dist/protocols/utils/open-responses-options.js +41 -0
  58. package/dist/protocols/utils/openai-image.d.ts +5 -0
  59. package/dist/protocols/utils/openai-image.js +18 -0
  60. package/dist/protocols/utils/openai-options.d.ts +14 -0
  61. package/dist/protocols/utils/openai-options.js +14 -0
  62. package/dist/protocols/utils/tool-schema.d.ts +8 -0
  63. package/dist/protocols/utils/tool-schema.js +81 -0
  64. package/dist/protocols/utils/tool-stream.d.ts +639 -0
  65. package/dist/protocols/utils/tool-stream.js +141 -0
  66. package/dist/protocols/xai-images.d.ts +26 -0
  67. package/dist/protocols/xai-images.js +111 -0
  68. package/dist/protocols/zai-images.d.ts +22 -0
  69. package/dist/protocols/zai-images.js +84 -0
  70. package/dist/protocols.d.ts +1 -0
  71. package/dist/protocols.js +1 -0
  72. package/dist/provider-error.d.ts +14 -0
  73. package/dist/provider-error.js +130 -0
  74. package/dist/provider-package.d.ts +15 -0
  75. package/dist/provider-package.js +1 -0
  76. package/dist/provider.d.ts +23 -0
  77. package/dist/provider.js +2 -0
  78. package/dist/providers/amazon-bedrock/mantle/chat.d.ts +2 -0
  79. package/dist/providers/amazon-bedrock/mantle/chat.js +1 -0
  80. package/dist/providers/amazon-bedrock/mantle/responses.d.ts +2 -0
  81. package/dist/providers/amazon-bedrock/mantle/responses.js +1 -0
  82. package/dist/providers/amazon-bedrock/mantle.d.ts +2 -0
  83. package/dist/providers/amazon-bedrock/mantle.js +1 -0
  84. package/dist/providers/amazon-bedrock-mantle.d.ts +247 -0
  85. package/dist/providers/amazon-bedrock-mantle.js +68 -0
  86. package/dist/providers/amazon-bedrock.d.ts +172 -0
  87. package/dist/providers/amazon-bedrock.js +41 -0
  88. package/dist/providers/anthropic-compatible.d.ts +218 -0
  89. package/dist/providers/anthropic-compatible.js +45 -0
  90. package/dist/providers/anthropic.d.ts +216 -0
  91. package/dist/providers/anthropic.js +40 -0
  92. package/dist/providers/azure/chat.d.ts +2 -0
  93. package/dist/providers/azure/chat.js +1 -0
  94. package/dist/providers/azure/responses.d.ts +2 -0
  95. package/dist/providers/azure/responses.js +1 -0
  96. package/dist/providers/azure.d.ts +248 -0
  97. package/dist/providers/azure.js +90 -0
  98. package/dist/providers/cloudflare.d.ts +338 -0
  99. package/dist/providers/cloudflare.js +87 -0
  100. package/dist/providers/google-vertex/chat.d.ts +2 -0
  101. package/dist/providers/google-vertex/chat.js +1 -0
  102. package/dist/providers/google-vertex/gemini.d.ts +2 -0
  103. package/dist/providers/google-vertex/gemini.js +1 -0
  104. package/dist/providers/google-vertex/messages.d.ts +2 -0
  105. package/dist/providers/google-vertex/messages.js +1 -0
  106. package/dist/providers/google-vertex/responses.d.ts +2 -0
  107. package/dist/providers/google-vertex/responses.js +1 -0
  108. package/dist/providers/google-vertex-chat.d.ts +131 -0
  109. package/dist/providers/google-vertex-chat.js +50 -0
  110. package/dist/providers/google-vertex-messages.d.ts +215 -0
  111. package/dist/providers/google-vertex-messages.js +75 -0
  112. package/dist/providers/google-vertex-responses.d.ts +122 -0
  113. package/dist/providers/google-vertex-responses.js +51 -0
  114. package/dist/providers/google-vertex-shared.d.ts +22 -0
  115. package/dist/providers/google-vertex-shared.js +61 -0
  116. package/dist/providers/google-vertex.d.ts +115 -0
  117. package/dist/providers/google-vertex.js +64 -0
  118. package/dist/providers/google.d.ts +112 -0
  119. package/dist/providers/google.js +43 -0
  120. package/dist/providers/index.d.ts +18 -0
  121. package/dist/providers/index.js +18 -0
  122. package/dist/providers/open-responses-options.d.ts +16 -0
  123. package/dist/providers/open-responses-options.js +1 -0
  124. package/dist/providers/openai/chat.d.ts +2 -0
  125. package/dist/providers/openai/chat.js +1 -0
  126. package/dist/providers/openai/responses.d.ts +2 -0
  127. package/dist/providers/openai/responses.js +1 -0
  128. package/dist/providers/openai-compatible/responses.d.ts +1 -0
  129. package/dist/providers/openai-compatible/responses.js +1 -0
  130. package/dist/providers/openai-compatible-profile.d.ts +43 -0
  131. package/dist/providers/openai-compatible-profile.js +12 -0
  132. package/dist/providers/openai-compatible-responses.d.ts +120 -0
  133. package/dist/providers/openai-compatible-responses.js +33 -0
  134. package/dist/providers/openai-compatible.d.ts +167 -0
  135. package/dist/providers/openai-compatible.js +55 -0
  136. package/dist/providers/openai-options.d.ts +21 -0
  137. package/dist/providers/openai-options.js +41 -0
  138. package/dist/providers/openai.d.ts +264 -0
  139. package/dist/providers/openai.js +83 -0
  140. package/dist/providers/openrouter.d.ts +537 -0
  141. package/dist/providers/openrouter.js +117 -0
  142. package/dist/providers/xai.d.ts +249 -0
  143. package/dist/providers/xai.js +79 -0
  144. package/dist/providers/zai.d.ts +24 -0
  145. package/dist/providers/zai.js +21 -0
  146. package/dist/providers.d.ts +1 -0
  147. package/dist/providers.js +1 -0
  148. package/dist/route/auth-options.d.ts +34 -0
  149. package/dist/route/auth-options.js +14 -0
  150. package/dist/route/auth.d.ts +51 -0
  151. package/dist/route/auth.js +89 -0
  152. package/dist/route/client.d.ts +348 -0
  153. package/dist/route/client.js +214 -0
  154. package/dist/route/endpoint.d.ts +28 -0
  155. package/dist/route/endpoint.js +21 -0
  156. package/dist/route/executor.d.ts +23 -0
  157. package/dist/route/executor.js +227 -0
  158. package/dist/route/framing.d.ts +23 -0
  159. package/dist/route/framing.js +4 -0
  160. package/dist/route/index.d.ts +16 -0
  161. package/dist/route/index.js +9 -0
  162. package/dist/route/protocol.d.ts +77 -0
  163. package/dist/route/protocol.js +17 -0
  164. package/dist/route/transport/http.d.ts +34 -0
  165. package/dist/route/transport/http.js +63 -0
  166. package/dist/route/transport/index.d.ts +39 -0
  167. package/dist/route/transport/index.js +4 -0
  168. package/dist/route/transport/websocket-channel.d.ts +56 -0
  169. package/dist/route/transport/websocket-channel.js +1 -0
  170. package/dist/route/transport/websocket.d.ts +55 -0
  171. package/dist/route/transport/websocket.js +326 -0
  172. package/dist/route.d.ts +1 -0
  173. package/dist/route.js +1 -0
  174. package/dist/schema/errors.d.ts +159 -0
  175. package/dist/schema/errors.js +155 -0
  176. package/dist/schema/events.d.ts +4587 -0
  177. package/dist/schema/events.js +492 -0
  178. package/dist/schema/ids.d.ts +30 -0
  179. package/dist/schema/ids.js +19 -0
  180. package/dist/schema/index.d.ts +5 -0
  181. package/dist/schema/index.js +5 -0
  182. package/dist/schema/messages.d.ts +402 -0
  183. package/dist/schema/messages.js +244 -0
  184. package/dist/schema/options.d.ts +159 -0
  185. package/dist/schema/options.js +202 -0
  186. package/dist/testing.d.ts +1493 -0
  187. package/dist/testing.js +88 -0
  188. package/dist/tool-runtime.d.ts +15 -0
  189. package/dist/tool-runtime.js +54 -0
  190. package/dist/tool.d.ts +135 -0
  191. package/dist/tool.js +66 -0
  192. package/dist/utils/record.d.ts +2 -0
  193. package/dist/utils/record.js +2 -0
  194. package/package.json +47 -0
@@ -0,0 +1,227 @@
1
+ import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect";
2
+ import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
3
+ import { HttpContext, HttpRateLimitDetails, HttpRequestDetails, HttpResponseDetails, AIError, TransportReason, } from "../schema/index.js";
4
+ import { classifyProviderFailure } from "../provider-error.js";
5
+ export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {
6
+ }
7
+ const headerDetails = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, String(value)]));
8
+ const normalizedHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
9
+ const requestId = (headers) => {
10
+ return (headers["x-request-id"] ??
11
+ headers["request-id"] ??
12
+ headers["x-amzn-requestid"] ??
13
+ headers["x-amz-request-id"] ??
14
+ headers["x-goog-request-id"] ??
15
+ headers["cf-ray"]);
16
+ };
17
+ const retryAfterMs = (headers) => {
18
+ const millis = Number(headers["retry-after-ms"]);
19
+ if (Number.isFinite(millis))
20
+ return Math.max(0, millis);
21
+ const value = headers["retry-after"];
22
+ if (!value)
23
+ return undefined;
24
+ const seconds = Number(value);
25
+ if (Number.isFinite(seconds))
26
+ return Math.max(0, seconds * 1000);
27
+ const date = Date.parse(value);
28
+ if (!Number.isNaN(date))
29
+ return Math.max(0, date - Date.now());
30
+ return undefined;
31
+ };
32
+ const addRateLimitValue = (target, key, value) => {
33
+ if (key.length > 0)
34
+ target[key] = value;
35
+ };
36
+ const rateLimitDetails = (headers, retryAfter) => {
37
+ const limit = {};
38
+ const remaining = {};
39
+ const reset = {};
40
+ Object.entries(headers).forEach(([name, value]) => {
41
+ const openaiLimit = /^x-ratelimit-limit-(.+)$/.exec(name)?.[1];
42
+ if (openaiLimit)
43
+ return addRateLimitValue(limit, openaiLimit, value);
44
+ const openaiRemaining = /^x-ratelimit-remaining-(.+)$/.exec(name)?.[1];
45
+ if (openaiRemaining)
46
+ return addRateLimitValue(remaining, openaiRemaining, value);
47
+ const openaiReset = /^x-ratelimit-reset-(.+)$/.exec(name)?.[1];
48
+ if (openaiReset)
49
+ return addRateLimitValue(reset, openaiReset, value);
50
+ const anthropic = /^anthropic-ratelimit-(.+)-(limit|remaining|reset)$/.exec(name);
51
+ if (!anthropic)
52
+ return;
53
+ if (anthropic[2] === "limit")
54
+ return addRateLimitValue(limit, anthropic[1], value);
55
+ if (anthropic[2] === "remaining")
56
+ return addRateLimitValue(remaining, anthropic[1], value);
57
+ return addRateLimitValue(reset, anthropic[1], value);
58
+ });
59
+ if (retryAfter === undefined &&
60
+ Object.keys(limit).length === 0 &&
61
+ Object.keys(remaining).length === 0 &&
62
+ Object.keys(reset).length === 0)
63
+ return undefined;
64
+ return new HttpRateLimitDetails({
65
+ retryAfterMs: retryAfter,
66
+ limit: Object.keys(limit).length === 0 ? undefined : limit,
67
+ remaining: Object.keys(remaining).length === 0 ? undefined : remaining,
68
+ reset: Object.keys(reset).length === 0 ? undefined : reset,
69
+ });
70
+ };
71
+ const requestDetails = (request) => new HttpRequestDetails({
72
+ method: request.method,
73
+ url: request.url,
74
+ headers: headerDetails(request.headers),
75
+ });
76
+ const responseDetails = (response) => new HttpResponseDetails({
77
+ status: response.status,
78
+ headers: headerDetails(response.headers),
79
+ });
80
+ const responseBody = (body) => {
81
+ if (body === undefined)
82
+ return {};
83
+ return { body };
84
+ };
85
+ const decodeProviderBody = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({
86
+ message: Schema.optionalKey(Schema.String),
87
+ error: Schema.optionalKey(Schema.Struct({ message: Schema.optionalKey(Schema.String) })),
88
+ })));
89
+ const providerMessage = (status, body) => {
90
+ const decoded = body === undefined ? undefined : Option.getOrUndefined(decodeProviderBody(body));
91
+ return ([decoded?.error?.message, decoded?.message].find((message) => message?.trim()) ??
92
+ `Provider request failed with HTTP ${status}`);
93
+ };
94
+ const responseHttp = (input) => new HttpContext({
95
+ request: requestDetails(input.request),
96
+ response: responseDetails(input.response),
97
+ ...input.body,
98
+ requestId: input.requestId,
99
+ rateLimit: input.rateLimit,
100
+ });
101
+ const statusError = (request) => (response) => Effect.gen(function* () {
102
+ if (response.status < 400)
103
+ return response;
104
+ const body = yield* response.text.pipe(Effect.catch(() => Effect.void));
105
+ const headers = normalizedHeaders(response.headers);
106
+ const retryAfter = retryAfterMs(headers);
107
+ const rateLimit = rateLimitDetails(headers, retryAfter);
108
+ const details = responseBody(body);
109
+ return yield* new AIError({
110
+ module: "RequestExecutor",
111
+ method: "execute",
112
+ reason: classifyProviderFailure({
113
+ status: response.status,
114
+ message: providerMessage(response.status, body),
115
+ retryAfterMs: retryAfter,
116
+ rateLimit,
117
+ http: responseHttp({
118
+ request,
119
+ response,
120
+ body: details,
121
+ requestId: requestId(headers),
122
+ rateLimit,
123
+ }),
124
+ }),
125
+ });
126
+ });
127
+ // Classifies an HTTP failure captured outside the executor (for example by the
128
+ // AI SDK's own fetch) onto the same reason types and HttpContext that
129
+ // executor-driven requests produce. The originating request is not available on
130
+ // that path, so the method is assumed (language model calls are always POST),
131
+ // request headers are empty.
132
+ export const classifyHttpFailure = (input) => {
133
+ const headers = normalizedHeaders(Headers.fromInput(input.responseHeaders));
134
+ const retryAfter = retryAfterMs(headers);
135
+ const rateLimit = rateLimitDetails(headers, retryAfter);
136
+ const details = responseBody(input.responseBody);
137
+ return classifyProviderFailure({
138
+ message: input.message,
139
+ status: input.status,
140
+ code: input.code,
141
+ retryAfterMs: retryAfter,
142
+ rateLimit,
143
+ http: new HttpContext({
144
+ request: new HttpRequestDetails({ method: "POST", url: input.url, headers: {} }),
145
+ response: input.status === undefined
146
+ ? undefined
147
+ : new HttpResponseDetails({ status: input.status, headers: headerDetails(Headers.fromInput(headers)) }),
148
+ ...details,
149
+ requestId: requestId(headers),
150
+ rateLimit,
151
+ }),
152
+ });
153
+ };
154
+ const NativeTransportFailure = Schema.Struct({
155
+ message: Schema.String,
156
+ code: Schema.optionalKey(Schema.String),
157
+ cause: Schema.optionalKey(Schema.Unknown),
158
+ });
159
+ const decodeNativeTransportFailure = Schema.decodeUnknownOption(NativeTransportFailure);
160
+ const nativeTransportFailure = (error) => {
161
+ const failure = Option.getOrUndefined(decodeNativeTransportFailure(error));
162
+ if (!failure)
163
+ return undefined;
164
+ if (failure.code !== undefined)
165
+ return failure;
166
+ const cause = Option.getOrUndefined(decodeNativeTransportFailure(failure.cause));
167
+ if (cause?.code !== undefined)
168
+ return cause;
169
+ return failure;
170
+ };
171
+ const httpError = (input) => {
172
+ const request = HttpClientError.isHttpClientError(input.error) ? input.error.request : input.request;
173
+ const transportError = (failure) => new AIError({
174
+ module: "RequestExecutor",
175
+ method: input.operation,
176
+ reason: new TransportReason({
177
+ message: failure.message,
178
+ transport: "http",
179
+ operation: input.operation,
180
+ code: failure.code,
181
+ url: request.url,
182
+ http: new HttpContext({ request: requestDetails(request) }),
183
+ }),
184
+ });
185
+ const source = HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
186
+ ? input.error.reason.cause
187
+ : input.error;
188
+ const native = nativeTransportFailure(source);
189
+ const code = native?.code;
190
+ const raw = native?.message ?? (input.error instanceof Error ? input.error.message : undefined);
191
+ const detail = raw;
192
+ const message = code && detail && !detail.includes(code) ? `${code}: ${detail}` : detail;
193
+ if (Cause.isTimeoutError(input.error) || Cause.isTimeoutError(source))
194
+ return transportError({ message: message ?? "HTTP transport timed out", code: code ?? "Timeout" });
195
+ if (!HttpClientError.isHttpClientError(input.error))
196
+ return transportError({ message: message ?? "HTTP transport failed", code });
197
+ if (input.error.reason._tag === "TransportError") {
198
+ return transportError({
199
+ message: message ?? input.error.reason.description ?? "HTTP transport failed",
200
+ code: code ?? input.error.reason._tag,
201
+ });
202
+ }
203
+ return transportError({
204
+ message: message ?? `HTTP transport failed: ${input.error.reason._tag}`,
205
+ code: code ?? input.error.reason._tag,
206
+ });
207
+ };
208
+ export const stream = (executor, request, middleware) => Stream.unwrap(Effect.gen(function* () {
209
+ const response = yield* executor.execute(request, middleware);
210
+ return response.stream.pipe(Stream.mapError((error) => httpError({ error, request: response.request, operation: "read" })));
211
+ }));
212
+ export const layer = Layer.effect(Service, Effect.gen(function* () {
213
+ const http = yield* HttpClient.HttpClient;
214
+ const executeOnce = (request, middleware) => Effect.gen(function* () {
215
+ if (!middleware)
216
+ return yield* http.execute(request).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })), Effect.flatMap(statusError(request)));
217
+ const response = yield* middleware(request, (input) => http
218
+ .execute(input)
219
+ .pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))))).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })));
220
+ return yield* statusError(response.request)(response);
221
+ });
222
+ return Service.of({
223
+ execute: executeOnce,
224
+ });
225
+ }));
226
+ export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer));
227
+ export * as RequestExecutor from "./executor.js";
@@ -0,0 +1,23 @@
1
+ import type { Stream } from "effect";
2
+ import type { AIError } from "../schema/index.js";
3
+ /**
4
+ * Decode a streaming HTTP response body into provider-protocol frames.
5
+ *
6
+ * `Framing` is the byte-stream-shaped seam between transport and protocol:
7
+ *
8
+ * - SSE (`Framing.sse`) — UTF-8 decode the body, run the SSE channel decoder,
9
+ * drop empty / `[DONE]` keep-alives. Each emitted frame is the JSON `data:`
10
+ * payload of one event.
11
+ * - AWS event stream — length-prefixed binary frames with CRC checksums.
12
+ * Each emitted frame is one parsed binary event record.
13
+ *
14
+ * The frame type is opaque to this layer; the protocol's `decode` step turns
15
+ * a frame into a typed chunk.
16
+ */
17
+ export interface Definition<Frame> {
18
+ readonly id: string;
19
+ readonly frame: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<Frame, AIError>;
20
+ }
21
+ /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
22
+ export declare const sse: Definition<string>;
23
+ export * as Framing from "./framing.js";
@@ -0,0 +1,4 @@
1
+ import * as ProviderShared from "../protocols/shared.js";
2
+ /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
3
+ export const sse = { id: "sse", frame: ProviderShared.sseFraming };
4
+ export * as Framing from "./framing.js";
@@ -0,0 +1,16 @@
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, } from "./client.js";
3
+ export * from "./executor.js";
4
+ export { Auth } from "./auth.js";
5
+ export { AuthOptions } from "./auth-options.js";
6
+ export { Endpoint } from "./endpoint.js";
7
+ export { Framing } from "./framing.js";
8
+ export { Protocol } from "./protocol.js";
9
+ export { HttpTransport, WebSocketTransport } from "./transport/index.js";
10
+ export * as Transport from "./transport/index.js";
11
+ export type { Definition as AuthShape, AuthInput, Credential, CredentialError } from "./auth.js";
12
+ export type { ApiKeyMode, AuthOverride, ProviderAuthOption } from "./auth-options.js";
13
+ export type { Definition as EndpointFn, EndpointInput } from "./endpoint.js";
14
+ export type { Definition as FramingDef } from "./framing.js";
15
+ export type { Protocol as ProtocolDef } from "./protocol.js";
16
+ export type { ChannelCheckpoint, ChannelCreate, ChannelObservation, HttpHandler, HttpMiddleware, Transport as TransportDef, TransportExecuteOptions, TransportExecution, TransportRuntime, WebSocketConnection, WebSocketChannelDriver, WebSocketChannelExchange, WebSocketChannelExecution, WebSocketChannelExecutor, WebSocketConnector, WebSocketRequest, } from "./transport/index.js";
@@ -0,0 +1,9 @@
1
+ export { Route, LLMClient } from "./client.js";
2
+ export * from "./executor.js";
3
+ export { Auth } from "./auth.js";
4
+ export { AuthOptions } from "./auth-options.js";
5
+ export { Endpoint } from "./endpoint.js";
6
+ export { Framing } from "./framing.js";
7
+ export { Protocol } from "./protocol.js";
8
+ export { HttpTransport, WebSocketTransport } from "./transport/index.js";
9
+ export * as Transport from "./transport/index.js";
@@ -0,0 +1,77 @@
1
+ import { Schema, type Effect } from "effect";
2
+ import type { AIError, LLMEvent, LLMRequest, ProtocolID } from "../schema/index.js";
3
+ /**
4
+ * The semantic API contract of one model server family.
5
+ *
6
+ * A `Protocol` owns the parts of a route that are intrinsic to "what does
7
+ * this API look like": how a common `LLMRequest` becomes a provider-native
8
+ * body, what schema that body must satisfy before it is JSON-encoded, and
9
+ * how the streaming response decodes back into common `LLMEvent`s.
10
+ *
11
+ * Examples:
12
+ *
13
+ * - `OpenAIChat.protocol` — chat completions style
14
+ * - `OpenResponses.protocol` — provider-neutral Responses API baseline
15
+ * - `OpenAIResponses.protocol` — OpenAI extensions to that baseline
16
+ * - `AnthropicMessages.protocol` — messages API with content blocks
17
+ * - `Gemini.protocol` — generateContent
18
+ * - `BedrockConverse.protocol` — Converse with binary event-stream framing
19
+ *
20
+ * A `Protocol` is **not** a deployment. It does not know which URL, which
21
+ * headers, or which auth scheme to use. Those are deployment concerns owned
22
+ * by `Route.make(...)` along with the chosen `Endpoint`, `Auth`,
23
+ * and `Framing`. This separation is what lets DeepSeek, TogetherAI, Cerebras,
24
+ * etc. all reuse `OpenAIChat.protocol` without forking 300 lines per provider.
25
+ *
26
+ * The four type parameters reflect the pipeline:
27
+ *
28
+ * - `Body` — provider-native request body candidate. `Route.make(...)`
29
+ * validates and JSON-encodes it with `body.schema`.
30
+ * - `Frame` — one unit of the framed response stream. SSE: a JSON data
31
+ * string. AWS event stream: a parsed binary frame.
32
+ * - `Event` — schema-decoded provider event produced from one frame.
33
+ * - `State` — accumulator threaded through `stream.step` to translate event
34
+ * sequences into `LLMEvent` sequences.
35
+ */
36
+ export interface Protocol<Body, Frame, Event, State> {
37
+ /** Stable id for the wire protocol implementation. */
38
+ readonly id: ProtocolID;
39
+ /** Request side: schema for the provider-native body and how to build it. */
40
+ readonly body: ProtocolBody<Body>;
41
+ /** Response side: streaming state machine. */
42
+ readonly stream: ProtocolStream<Frame, Event, State>;
43
+ }
44
+ export interface ProtocolBody<Body> {
45
+ /** Schema for the validated provider-native body sent as the JSON request. */
46
+ readonly schema: Schema.Codec<Body, unknown>;
47
+ /** Build the provider-native body from a common `LLMRequest`. */
48
+ readonly from: (request: LLMRequest) => Effect.Effect<Body, AIError>;
49
+ }
50
+ export interface ProtocolStream<Frame, Event, State> {
51
+ /** Schema for one decoded streaming event, decoded from a transport frame. */
52
+ readonly event: Schema.Codec<Event, Frame>;
53
+ /** Initial parser state. Called once per response with the resolved request. */
54
+ readonly initial: (request: LLMRequest) => State;
55
+ /** Translate one event into emitted `LLMEvent`s plus the next state. */
56
+ readonly step: (state: State, event: Event) => Effect.Effect<readonly [State, ReadonlyArray<LLMEvent>], AIError>;
57
+ /** Optional request-completion signal for transports that do not end naturally. */
58
+ readonly terminal?: (event: Event) => boolean;
59
+ /** Optional flush emitted when the framed stream ends. */
60
+ readonly onHalt?: (state: State) => ReadonlyArray<LLMEvent>;
61
+ }
62
+ /**
63
+ * Construct a `Protocol` from its body and stream pieces:
64
+ *
65
+ * - `body.schema` infers the provider-native request body shape.
66
+ * - `body.from` ties the common `LLMRequest` to the provider body.
67
+ * - `stream.event` infers the decoded streaming event and the wire frame.
68
+ * - `stream.initial`, `stream.step`, and `stream.onHalt` infer the parser state.
69
+ *
70
+ * Provider implementations should usually call `Protocol.make({ ... })`
71
+ * without explicit type arguments; the schemas and parser functions are the
72
+ * source of truth. The constructor remains as the public seam for future
73
+ * cross-cutting concerns such as tracing or instrumentation.
74
+ */
75
+ export declare const make: <Body, Frame, Event, State>(input: Protocol<Body, Frame, Event, State>) => Protocol<Body, Frame, Event, State>;
76
+ export declare const jsonEvent: <const S extends Schema.Top>(schema: S) => Schema.fromJsonString<S>;
77
+ export * as Protocol from "./protocol.js";
@@ -0,0 +1,17 @@
1
+ import { Schema } from "effect";
2
+ /**
3
+ * Construct a `Protocol` from its body and stream pieces:
4
+ *
5
+ * - `body.schema` infers the provider-native request body shape.
6
+ * - `body.from` ties the common `LLMRequest` to the provider body.
7
+ * - `stream.event` infers the decoded streaming event and the wire frame.
8
+ * - `stream.initial`, `stream.step`, and `stream.onHalt` infer the parser state.
9
+ *
10
+ * Provider implementations should usually call `Protocol.make({ ... })`
11
+ * without explicit type arguments; the schemas and parser functions are the
12
+ * source of truth. The constructor remains as the public seam for future
13
+ * cross-cutting concerns such as tracing or instrumentation.
14
+ */
15
+ export const make = (input) => input;
16
+ export const jsonEvent = (schema) => Schema.fromJsonString(schema);
17
+ export * as Protocol from "./protocol.js";
@@ -0,0 +1,34 @@
1
+ import { Effect } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { Framing } from "../framing.js";
4
+ import type { HttpMiddleware, Transport, TransportPrepareInput } from "./index.js";
5
+ export type JsonRequestInput<Body> = TransportPrepareInput<Body>;
6
+ export interface JsonRequestParts<Body = unknown> {
7
+ readonly url: string;
8
+ readonly jsonBody: Body | Record<string, unknown>;
9
+ readonly bodyText: string;
10
+ readonly headers: Headers.Headers;
11
+ }
12
+ export interface HttpPrepared<Frame> {
13
+ readonly request: HttpClientRequest.HttpClientRequest;
14
+ readonly framing: Framing.Definition<Frame>;
15
+ readonly middleware?: HttpMiddleware;
16
+ }
17
+ export declare const jsonRequestParts: <Body>(input: JsonRequestInput<Body>) => Effect.Effect<{
18
+ url: string;
19
+ jsonBody: Record<string, unknown> | Body;
20
+ bodyText: string;
21
+ headers: Headers.Headers;
22
+ }, import("../../schema/errors.js").AIError, never>;
23
+ export interface HttpJsonInput<_Body, Frame> {
24
+ readonly framing: Framing.Definition<Frame>;
25
+ }
26
+ export type HttpJsonPatch<Body, Frame> = Partial<HttpJsonInput<Body, Frame>>;
27
+ export interface HttpJsonTransport<Body, Frame> extends Transport<Body, HttpPrepared<Frame>, Frame> {
28
+ readonly with: (patch: HttpJsonPatch<Body, Frame>) => HttpJsonTransport<Body, Frame>;
29
+ }
30
+ export declare const httpJson: <Body, Frame>(input: HttpJsonInput<Body, Frame>) => HttpJsonTransport<Body, Frame>;
31
+ export declare const sseJson: {
32
+ readonly id: "http-json/sse";
33
+ readonly with: <Body>() => HttpJsonTransport<Body, string>;
34
+ };
@@ -0,0 +1,63 @@
1
+ import { Effect } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { Auth } from "../auth.js";
4
+ import { render as renderEndpoint } from "../endpoint.js";
5
+ import { Framing } from "../framing.js";
6
+ import * as ProviderShared from "../../protocols/shared.js";
7
+ import { mergeJsonRecords } from "../../schema/index.js";
8
+ import { RequestExecutor } from "../executor.js";
9
+ const applyQuery = (url, query) => {
10
+ if (!query)
11
+ return url;
12
+ const next = new URL(url);
13
+ Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
14
+ return next.toString();
15
+ };
16
+ const bodyWithOverlay = (body, request, encodeBody) => Effect.gen(function* () {
17
+ if (request.http?.body === undefined)
18
+ return { jsonBody: body, bodyText: encodeBody(body) };
19
+ if (ProviderShared.isRecord(body)) {
20
+ const overlaid = mergeJsonRecords(body, request.http.body) ?? {};
21
+ return { jsonBody: overlaid, bodyText: ProviderShared.encodeJson(overlaid) };
22
+ }
23
+ return yield* ProviderShared.invalidRequest("http.body can only overlay JSON object request bodies");
24
+ });
25
+ export const jsonRequestParts = (input) => Effect.gen(function* () {
26
+ const url = applyQuery(renderEndpoint(input.endpoint, { request: input.request, body: input.body }).toString(), input.request.http?.query);
27
+ const body = yield* bodyWithOverlay(input.body, input.request, input.encodeBody);
28
+ const headers = yield* Auth.toEffect(input.auth)({
29
+ request: input.request,
30
+ method: "POST",
31
+ url,
32
+ body: body.bodyText,
33
+ headers: Headers.fromInput({
34
+ ...input.headers?.({ request: input.request }),
35
+ ...input.request.http?.headers,
36
+ }),
37
+ });
38
+ return { url, jsonBody: body.jsonBody, bodyText: body.bodyText, headers };
39
+ });
40
+ export const httpJson = (input) => ({
41
+ id: "http-json",
42
+ with: (patch) => httpJson({ ...input, ...patch }),
43
+ prepare: (prepareInput) => Effect.gen(function* () {
44
+ const parts = yield* jsonRequestParts({ ...prepareInput });
45
+ const request = ProviderShared.jsonPost({
46
+ url: parts.url,
47
+ body: parts.bodyText,
48
+ headers: parts.headers,
49
+ });
50
+ return {
51
+ request,
52
+ framing: input.framing,
53
+ middleware: prepareInput.middleware,
54
+ };
55
+ }),
56
+ execute: (prepared, _request, runtime) => Effect.succeed({
57
+ frames: prepared.framing.frame(RequestExecutor.stream(runtime.http, prepared.request, prepared.middleware)),
58
+ }),
59
+ });
60
+ export const sseJson = {
61
+ id: "http-json/sse",
62
+ with: () => httpJson({ framing: Framing.sse }),
63
+ };
@@ -0,0 +1,39 @@
1
+ import type { Effect, Scope, Stream } from "effect";
2
+ import { Endpoint } from "../endpoint.js";
3
+ import { Auth } from "../auth.js";
4
+ import type { HttpMiddleware, Interface as RequestExecutorInterface } from "../executor.js";
5
+ import type { WebSocketChannelExecutor } from "./websocket-channel.js";
6
+ import type { AIError, LLMRequest } from "../../schema/index.js";
7
+ export interface TransportRuntime {
8
+ readonly http: RequestExecutorInterface;
9
+ }
10
+ export interface TransportExecution<Frame> {
11
+ readonly frames: Stream.Stream<Frame, AIError>;
12
+ /** Optional successful-consumption acknowledgement. HTTP leaves this absent. */
13
+ readonly complete?: Effect.Effect<void>;
14
+ }
15
+ export interface TransportExecuteOptions {
16
+ readonly webSocket?: WebSocketChannelExecutor;
17
+ }
18
+ export interface Transport<Body, Prepared, Frame> {
19
+ readonly id: string;
20
+ readonly prepare: (input: TransportPrepareInput<Body>) => Effect.Effect<Prepared, AIError>;
21
+ readonly execute: (prepared: Prepared, request: LLMRequest, runtime: TransportRuntime, options?: TransportExecuteOptions) => Effect.Effect<TransportExecution<Frame>, AIError, Scope.Scope>;
22
+ }
23
+ export interface TransportPrepareInput<Body> {
24
+ readonly body: Body;
25
+ readonly request: LLMRequest;
26
+ readonly endpoint: Endpoint.Definition<Body>;
27
+ readonly auth: Auth.Definition;
28
+ readonly encodeBody: (body: Body) => string;
29
+ readonly headers?: (input: {
30
+ readonly request: LLMRequest;
31
+ }) => Record<string, string>;
32
+ readonly middleware?: HttpMiddleware;
33
+ readonly webSocket?: WebSocketChannelExecutor;
34
+ }
35
+ export * as HttpTransport from "./http.js";
36
+ export type { HttpHandler, HttpMiddleware } from "../executor.js";
37
+ export type { ChannelCheckpoint, ChannelCreate, ChannelObservation, WebSocketChannelDriver, WebSocketChannelExchange, WebSocketChannelExecution, WebSocketChannelExecutor, } from "./websocket-channel.js";
38
+ export type { WebSocketConnection, WebSocketConnector, WebSocketRequest } from "./websocket.js";
39
+ export { WebSocketTransport } from "./websocket.js";
@@ -0,0 +1,4 @@
1
+ import { Endpoint } from "../endpoint.js";
2
+ import { Auth } from "../auth.js";
3
+ export * as HttpTransport from "./http.js";
4
+ export { WebSocketTransport } from "./websocket.js";
@@ -0,0 +1,56 @@
1
+ import type { Effect, Scope, Stream } from "effect";
2
+ import type { Headers } from "effect/unstable/http";
3
+ import type { AIError } from "../../schema/index.js";
4
+ export interface WebSocketChannelExecutor {
5
+ readonly execute: (exchange: WebSocketChannelExchange) => Effect.Effect<WebSocketChannelExecution, AIError, Scope.Scope>;
6
+ }
7
+ export interface WebSocketChannelExecution {
8
+ readonly frames: Stream.Stream<string, AIError>;
9
+ /** Commits staged state after the decoded Route stream ends successfully. */
10
+ readonly complete: Effect.Effect<void>;
11
+ }
12
+ export interface WebSocketChannelExchange {
13
+ readonly id: string;
14
+ readonly connect: {
15
+ readonly url: string;
16
+ readonly headers: Headers.Headers;
17
+ /** Provider-safe connection age after which Core should rotate before sending. */
18
+ readonly rotateAfterMs?: number;
19
+ };
20
+ readonly fallback: () => Stream.Stream<string, AIError>;
21
+ readonly driver: WebSocketChannelDriver;
22
+ }
23
+ export interface WebSocketChannelDriver {
24
+ readonly create: (checkpoint: ChannelCheckpoint | undefined) => Effect.Effect<ChannelCreate, AIError>;
25
+ readonly observe: (create: ChannelCreate, frame: string) => Effect.Effect<ChannelObservation, AIError>;
26
+ }
27
+ export interface ChannelCreate {
28
+ readonly message: string;
29
+ readonly mode: "full" | "incremental";
30
+ }
31
+ export type ChannelObservation = {
32
+ readonly type: "frame";
33
+ readonly frame: string;
34
+ } | {
35
+ readonly type: "completed";
36
+ readonly frame: string;
37
+ readonly checkpoint?: ChannelCheckpoint;
38
+ } | {
39
+ readonly type: "incomplete";
40
+ readonly frame: string;
41
+ } | {
42
+ readonly type: "provider-failure";
43
+ readonly error: AIError;
44
+ } | {
45
+ readonly type: "rejected";
46
+ readonly error: AIError;
47
+ readonly recovery: "retry-full";
48
+ } | {
49
+ readonly type: "rejected";
50
+ readonly error: AIError;
51
+ readonly recovery: "rotate-and-retry-full";
52
+ };
53
+ export interface ChannelCheckpoint {
54
+ readonly protocol: string;
55
+ readonly value: unknown;
56
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { Headers } from "effect/unstable/http";
3
+ import { Socket } from "effect/unstable/socket";
4
+ import { AIError } from "../../schema/index.js";
5
+ import type { Transport } from "./index.js";
6
+ import type { WebSocketChannelExecutor } from "./websocket-channel.js";
7
+ export interface WebSocketRequest {
8
+ readonly url: string;
9
+ readonly headers: Headers.Headers;
10
+ }
11
+ export interface WebSocketConnection {
12
+ readonly sendText: (message: string) => Effect.Effect<void, AIError>;
13
+ readonly messages: Stream.Stream<string | Uint8Array, AIError>;
14
+ readonly close: Effect.Effect<void, never>;
15
+ }
16
+ export interface WebSocketConnector {
17
+ readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
18
+ }
19
+ export declare const toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
20
+ export declare const open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
21
+ export declare const fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
22
+ export declare const messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
23
+ export declare const makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
24
+ export declare const direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
25
+ export interface JsonPrepared {
26
+ readonly url: string;
27
+ readonly headers: Headers.Headers;
28
+ readonly message: string;
29
+ }
30
+ export interface JsonInput<Body, Message> {
31
+ readonly toMessage: (body: Body | Record<string, unknown>) => Effect.Effect<Message, AIError>;
32
+ readonly encodeMessage: (message: Message) => string;
33
+ }
34
+ export type JsonPatch<Body, Message> = Partial<JsonInput<Body, Message>>;
35
+ export interface JsonTransport<Body, Message> extends Transport<Body, JsonPrepared, string> {
36
+ readonly with: (patch: JsonPatch<Body, Message>) => JsonTransport<Body, Message>;
37
+ }
38
+ export declare const json: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
39
+ export declare const jsonTransport: {
40
+ readonly id: "websocket-json";
41
+ readonly with: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
42
+ };
43
+ export declare const WebSocketTransport: {
44
+ readonly json: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
45
+ readonly jsonTransport: {
46
+ readonly id: "websocket-json";
47
+ readonly with: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
48
+ };
49
+ readonly direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
50
+ readonly makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
51
+ readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
52
+ readonly fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
53
+ readonly messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
54
+ readonly toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
55
+ };