@opencode-ai/ai 0.0.0-beta-18414 → 0.0.0-beta-18600

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.
@@ -158,13 +158,13 @@ export const errorText = (error) => {
158
158
  };
159
159
  /**
160
160
  * `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
161
- * decoder, optionally filters named events, and drops empty / `[DONE]`
162
- * keep-alive events so the protocol event schema sees one JSON string per
163
- * element. Retry control events are ignored without interrupting the stream.
161
+ * decoder, optionally filters named events, and drops empty events. `[DONE]`
162
+ * is dropped by default or retained for protocols that use it as their stream
163
+ * boundary. Retry control events are ignored without interrupting the stream.
164
164
  * Decoder failures become provider output errors so the public error channel
165
165
  * stays `AIError`.
166
166
  */
167
- export const sseFraming = (bytes, events) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
167
+ export const sseFraming = (bytes, events, includeDone = false) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
168
168
  const output = [];
169
169
  return {
170
170
  output,
@@ -180,7 +180,7 @@ export const sseFraming = (bytes, events) => bytes.pipe(Stream.decodeText(), Str
180
180
  return [state, state.output.splice(0)];
181
181
  })), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
182
182
  event.data.length > 0 &&
183
- (event.data !== "[DONE]" || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
183
+ (event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
184
184
  /**
185
185
  * Canonical invalid-request constructor shared by protocol lowering.
186
186
  */
@@ -10,8 +10,12 @@ export declare const textStart: (state: State, events: LLMEvent[], id: string, p
10
10
  export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
11
11
  export declare const reasoningStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
12
12
  export declare const reasoningDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
13
- export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
14
- export declare const textEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
13
+ export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata,
14
+ /** Authoritative complete value; replaces accumulated deltas when present. */
15
+ text?: string) => State;
16
+ export declare const textEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata,
17
+ /** Authoritative complete value; replaces accumulated deltas when present. */
18
+ text?: string) => State;
15
19
  export declare const finish: (state: State, events: LLMEvent[], input: {
16
20
  readonly reason: FinishReasonDetails;
17
21
  readonly usage?: Usage;
@@ -30,23 +30,27 @@ export const reasoningDelta = (state, events, id, text, providerMetadata) => {
30
30
  events.push(LLMEvent.reasoningDelta({ id, text, providerMetadata }));
31
31
  return started;
32
32
  };
33
- export const reasoningEnd = (state, events, id, providerMetadata) => {
33
+ export const reasoningEnd = (state, events, id, providerMetadata,
34
+ /** Authoritative complete value; replaces accumulated deltas when present. */
35
+ text) => {
34
36
  if (!state.reasoning.has(id))
35
37
  return state;
36
38
  const stepped = stepStart(state, events);
37
- events.push(LLMEvent.reasoningEnd({ id, providerMetadata }));
39
+ events.push(LLMEvent.reasoningEnd({ id, text, providerMetadata }));
38
40
  const reasoning = new Set(stepped.reasoning);
39
41
  reasoning.delete(id);
40
42
  return { ...stepped, reasoning };
41
43
  };
42
- export const textEnd = (state, events, id, providerMetadata) => {
44
+ export const textEnd = (state, events, id, providerMetadata,
45
+ /** Authoritative complete value; replaces accumulated deltas when present. */
46
+ text) => {
43
47
  if (!state.text.has(id))
44
48
  return state;
45
49
  const stepped = stepStart(state, events);
46
- events.push(LLMEvent.textEnd({ id, providerMetadata }));
47
- const text = new Set(stepped.text);
48
- text.delete(id);
49
- return { ...stepped, text };
50
+ events.push(LLMEvent.textEnd({ id, text, providerMetadata }));
51
+ const open = new Set(stepped.text);
52
+ open.delete(id);
53
+ return { ...stepped, text: open };
50
54
  };
51
55
  const closeOpenBlocks = (state, events) => {
52
56
  for (const id of state.reasoning)
@@ -93,6 +93,7 @@ export declare const finish: <K extends StreamKey>(route: string, tools: State<K
93
93
  } | {
94
94
  readonly id: string;
95
95
  readonly type: "text-end";
96
+ readonly text?: string | undefined;
96
97
  readonly providerMetadata?: {
97
98
  readonly [x: string]: {
98
99
  readonly [x: string]: unknown;
@@ -118,6 +119,7 @@ export declare const finish: <K extends StreamKey>(route: string, tools: State<K
118
119
  } | {
119
120
  readonly id: string;
120
121
  readonly type: "reasoning-end";
122
+ readonly text?: string | undefined;
121
123
  readonly providerMetadata?: {
122
124
  readonly [x: string]: {
123
125
  readonly [x: string]: unknown;
@@ -287,6 +289,7 @@ export declare const finishWithInput: <K extends StreamKey>(route: string, tools
287
289
  } | {
288
290
  readonly id: string;
289
291
  readonly type: "text-end";
292
+ readonly text?: string | undefined;
290
293
  readonly providerMetadata?: {
291
294
  readonly [x: string]: {
292
295
  readonly [x: string]: unknown;
@@ -312,6 +315,7 @@ export declare const finishWithInput: <K extends StreamKey>(route: string, tools
312
315
  } | {
313
316
  readonly id: string;
314
317
  readonly type: "reasoning-end";
318
+ readonly text?: string | undefined;
315
319
  readonly providerMetadata?: {
316
320
  readonly [x: string]: {
317
321
  readonly [x: string]: unknown;
@@ -478,6 +482,7 @@ export declare const finishAll: <K extends StreamKey>(route: string, tools: Stat
478
482
  } | {
479
483
  readonly id: string;
480
484
  readonly type: "text-end";
485
+ readonly text?: string | undefined;
481
486
  readonly providerMetadata?: {
482
487
  readonly [x: string]: {
483
488
  readonly [x: string]: unknown;
@@ -503,6 +508,7 @@ export declare const finishAll: <K extends StreamKey>(route: string, tools: Stat
503
508
  } | {
504
509
  readonly id: string;
505
510
  readonly type: "reasoning-end";
511
+ readonly text?: string | undefined;
506
512
  readonly providerMetadata?: {
507
513
  readonly [x: string]: {
508
514
  readonly [x: string]: unknown;
@@ -24,6 +24,7 @@ const patterns = [
24
24
  /too large for model with \d+ maximum context length/i,
25
25
  /prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
26
26
  /model_context_window_exceeded/i,
27
+ /range of input length should be/i,
27
28
  /too many tokens/i,
28
29
  /token limit exceeded/i,
29
30
  /request_too_large/i,
@@ -38,6 +39,7 @@ export const isContextOverflowFailure = (failure) => failure instanceof AIError
38
39
  : Schema.is(ProviderErrorEvent)(failure) && failure.classification === "context-overflow";
39
40
  const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
40
41
  const QUOTA_CODES = new Set(["insufficient_quota", "usage_not_included", "billing_error"]);
42
+ const AUTH_CODES = new Set(["authentication_error", "permission_error"]);
41
43
  const SERVER_CODES = new Set([
42
44
  "api_error",
43
45
  "internal_error",
@@ -53,9 +55,12 @@ const INVALID_REQUEST_CODES = new Set(["invalid_prompt", "invalid_request_error"
53
55
  const RATE_LIMIT_TEXT = /rate increased too quickly|rate[-_\s]?limit|too[_\s]?many[_\s]?requests/i;
54
56
  const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded/i;
55
57
  const CONTENT_POLICY_TEXT = /content[-_\s]?policy|content_filter|safety/i;
56
- const NETWORK_ERROR_TEXT = /network[-_\s]error/i;
57
- // Keep HTTP failures and provider-reported stream failures on one typed path so
58
- // session retry policy never needs provider-specific string matching.
58
+ // Classification records affirmative evidence about a failure. Deterministic
59
+ // failures need positive identification (a 4xx status, quota/auth/policy
60
+ // signals); anything unrecognized stays UnknownProvider, which the session
61
+ // retry policy treats as retry-eligible because transient failures arrive in
62
+ // unpredictable shapes while deterministic rejections almost always carry a
63
+ // status or known code.
59
64
  export function classifyProviderFailure(input) {
60
65
  const details = { message: input.message, body: input.rawBody, http: input.http, cause: input.cause };
61
66
  const body = input.rawBody ?? "";
@@ -76,48 +81,28 @@ export function classifyProviderFailure(input) {
76
81
  return new ContentPolicyError(details);
77
82
  if (codes.some((code) => QUOTA_CODES.has(code)) || (input.status === 429 && QUOTA_TEXT.test(text)))
78
83
  return new QuotaExceededError(details);
79
- if (input.status === 401)
80
- return new AuthenticationError({ ...details, kind: "invalid" });
81
- if (input.status === 403)
82
- return new AuthenticationError({ ...details, kind: "insufficient-permissions" });
83
- if (codes.includes("authentication_error"))
84
- return new AuthenticationError({ ...details, kind: "invalid" });
85
- if (codes.includes("permission_error"))
86
- return new AuthenticationError({ ...details, kind: "insufficient-permissions" });
87
- if (codes.some((code) => code.includes("rate_limit") || code === "too_many_requests" || code === "throttlingexception"))
84
+ if (input.status === 401 || input.status === 403 || codes.some((code) => AUTH_CODES.has(code)))
85
+ return new AuthenticationError(details);
86
+ if (input.status === 429 ||
87
+ codes.some((code) => code.includes("rate_limit") || code === "too_many_requests" || code === "throttlingexception") ||
88
+ RATE_LIMIT_TEXT.test(text))
88
89
  return new RateLimitError({
89
90
  ...details,
90
91
  retryAfterMs: input.retryAfterMs,
91
92
  rateLimit: input.rateLimit,
92
93
  });
93
- if (RATE_LIMIT_TEXT.test(text))
94
- return new RateLimitError({
95
- ...details,
96
- retryAfterMs: input.retryAfterMs,
97
- rateLimit: input.rateLimit,
98
- });
99
- if (NETWORK_ERROR_TEXT.test(text))
100
- return new ProviderInternalError(details);
101
- if (codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))
102
- return new ProviderInternalError({
103
- ...details,
104
- retryAfterMs: input.retryAfterMs,
105
- });
106
- if (input.status === 429) {
107
- return new RateLimitError({
108
- ...details,
109
- retryAfterMs: input.retryAfterMs,
110
- rateLimit: input.rateLimit,
111
- });
112
- }
113
- if (input.status === 408 || input.status === 409 || (input.status !== undefined && input.status >= 500))
94
+ if (input.status === 408 ||
95
+ input.status === 409 ||
96
+ (input.status !== undefined && input.status >= 500) ||
97
+ codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))
114
98
  return new ProviderInternalError({
115
99
  ...details,
116
100
  retryAfterMs: input.retryAfterMs,
117
101
  });
118
102
  if (codes.some((code) => INVALID_REQUEST_CODES.has(code)))
119
103
  return new InvalidRequestError(details);
120
- if (input.status === 400 || input.status === 404 || input.status === 413 || input.status === 422)
104
+ // Any remaining 4xx is a deterministic rejection of this request.
105
+ if (input.status !== undefined && input.status >= 400 && input.status < 500)
121
106
  return new InvalidRequestError(details);
122
107
  return new UnknownProviderError(details);
123
108
  }
@@ -125,7 +125,7 @@ export declare const protocol: Protocol<{
125
125
  readonly parallel_tool_calls?: boolean | undefined;
126
126
  readonly reasoning_format?: "parsed" | undefined;
127
127
  readonly include_reasoning?: boolean | undefined;
128
- }, string, {
128
+ }, string, "[DONE]" | {
129
129
  readonly [x: string]: unknown;
130
130
  readonly error?: {
131
131
  readonly [x: string]: unknown;
@@ -4,7 +4,6 @@ import { ProviderShared } from "../protocols/shared.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 { Framing } from "../route/framing.js";
8
7
  import { Protocol } from "../route/protocol.js";
9
8
  import { ProviderID } from "../schema/index.js";
10
9
  import { profiles } from "./openai-compatible-profile.js";
@@ -47,7 +46,7 @@ export const route = Route.make({
47
46
  providerMetadataKey: "openai",
48
47
  protocol,
49
48
  endpoint: Endpoint.path("/chat/completions", { baseURL: profiles.groq.baseURL }),
50
- framing: Framing.sse,
49
+ framing: OpenAIChat.framing,
51
50
  });
52
51
  export const configure = (input = {}) => {
53
52
  const { apiKey: _apiKey, auth: _auth, baseURL, ...defaults } = input;
@@ -282,7 +282,7 @@ export declare const protocol: Protocol<{
282
282
  readonly tool_stream?: boolean | undefined;
283
283
  readonly frequency_penalty?: number | undefined;
284
284
  readonly presence_penalty?: number | undefined;
285
- }, string, {
285
+ }, string, "[DONE]" | {
286
286
  readonly [x: string]: unknown;
287
287
  readonly error?: {
288
288
  readonly [x: string]: unknown;
@@ -1,7 +1,6 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { Route } from "../route/client.js";
3
3
  import { Endpoint } from "../route/endpoint.js";
4
- import { Framing } from "../route/framing.js";
5
4
  import { Protocol } from "../route/protocol.js";
6
5
  import { AuthOptions } from "../route/auth-options.js";
7
6
  import { ProviderID } from "../schema/index.js";
@@ -87,7 +86,7 @@ export const route = Route.make({
87
86
  providerMetadataKey: "openrouter",
88
87
  protocol,
89
88
  endpoint: Endpoint.path("/chat/completions", { baseURL: profile.baseURL }),
90
- framing: Framing.sse,
89
+ framing: OpenAIChat.framing,
91
90
  });
92
91
  export const routes = [route];
93
92
  const configuredRoute = (input) => {
@@ -77,7 +77,7 @@ const toAIError = (error) => {
77
77
  if (error instanceof MissingCredentialError || error instanceof Config.ConfigError) {
78
78
  return new AIError({
79
79
  reason: error instanceof MissingCredentialError
80
- ? new AuthenticationError({ message: error.message, cause: error, kind: "missing" })
80
+ ? new AuthenticationError({ message: error.message, cause: error })
81
81
  : new InvalidRequestError({ message: `Failed to resolve auth config: ${error.message}`, cause: error }),
82
82
  });
83
83
  }
@@ -173,6 +173,7 @@ export declare const streamRequest: (request: LLMRequest, options?: StreamOption
173
173
  } | {
174
174
  readonly id: string;
175
175
  readonly type: "text-end";
176
+ readonly text?: string | undefined;
176
177
  readonly providerMetadata?: {
177
178
  readonly [x: string]: {
178
179
  readonly [x: string]: unknown;
@@ -198,6 +199,7 @@ export declare const streamRequest: (request: LLMRequest, options?: StreamOption
198
199
  } | {
199
200
  readonly id: string;
200
201
  readonly type: "reasoning-end";
202
+ readonly text?: string | undefined;
201
203
  readonly providerMetadata?: {
202
204
  readonly [x: string]: {
203
205
  readonly [x: string]: unknown;
@@ -6,8 +6,8 @@ import type { AIError } from "../schema/index.js";
6
6
  * `Framing` is the byte-stream-shaped seam between transport and protocol:
7
7
  *
8
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.
9
+ * and emit the `data:` payload of each non-empty event. The default drops
10
+ * `[DONE]`; protocols that use it as a terminal select `sseWithDone`.
11
11
  * - AWS event stream — length-prefixed binary frames with CRC checksums.
12
12
  * Each emitted frame is one parsed binary event record.
13
13
  *
@@ -22,6 +22,8 @@ export interface Definition<Frame> {
22
22
  }
23
23
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
24
24
  export declare const sse: Definition<string>;
25
+ /** Server-Sent Events framing that retains the conventional `[DONE]` sentinel. */
26
+ export declare const sseWithDone: Definition<string>;
25
27
  /** SSE framing restricted to protocol-recognized event names. */
26
28
  export declare const sseEvents: (events: ReadonlySet<string>) => Definition<string>;
27
29
  export * as Framing from "./framing.js";
@@ -1,6 +1,11 @@
1
1
  import * as ProviderShared from "../protocols/shared.js";
2
2
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
3
3
  export const sse = { id: "sse", frame: ProviderShared.sseFraming };
4
+ /** Server-Sent Events framing that retains the conventional `[DONE]` sentinel. */
5
+ export const sseWithDone = {
6
+ id: "sse",
7
+ frame: (bytes) => ProviderShared.sseFraming(bytes, undefined, true),
8
+ };
4
9
  /** SSE framing restricted to protocol-recognized event names. */
5
10
  export const sseEvents = (events) => ({
6
11
  id: "sse",
@@ -39,11 +39,10 @@ declare const NoRouteError_base: Schema.Class<NoRouteError, Schema.TaggedStruct<
39
39
  export declare class NoRouteError extends NoRouteError_base {
40
40
  }
41
41
  declare const AuthenticationError_base: Schema.Class<AuthenticationError, Schema.TaggedStruct<"Authentication", {
42
- readonly kind: Schema.Literals<readonly ["missing", "invalid", "expired", "insufficient-permissions", "unknown"]>;
43
- readonly message: Schema.String;
44
- readonly body: Schema.optional<Schema.String>;
45
- readonly http: Schema.optional<typeof HttpContext>;
46
- readonly cause: Schema.optional<Schema.Defect>;
42
+ message: Schema.String;
43
+ body: Schema.optional<Schema.String>;
44
+ http: Schema.optional<typeof HttpContext>;
45
+ cause: Schema.optional<Schema.Defect>;
47
46
  }>, import("effect/Cause").YieldableError>;
48
47
  export declare class AuthenticationError extends AuthenticationError_base {
49
48
  }
@@ -35,10 +35,7 @@ export class NoRouteError extends Schema.TaggedError("AI.Error.NoRoute")("NoRout
35
35
  model: ModelID,
36
36
  }) {
37
37
  }
38
- export class AuthenticationError extends Schema.TaggedError("AI.Error.Authentication")("Authentication", {
39
- ...ReasonFields,
40
- kind: Schema.Literals(["missing", "invalid", "expired", "insufficient-permissions", "unknown"]),
41
- }) {
38
+ export class AuthenticationError extends Schema.TaggedError("AI.Error.Authentication")("Authentication", ReasonFields) {
42
39
  }
43
40
  export class RateLimitError extends Schema.TaggedError("AI.Error.RateLimit")("RateLimit", {
44
41
  ...ReasonFields,