@opencode/ai 0.0.0-dev-19443 → 0.0.0-dev-19445

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.
@@ -1,5 +1,6 @@
1
1
  import { Headers } from "effect/unstable/http";
2
2
  import { HttpTransport, type Transport, type WebSocketChannelDriver } from "../route/transport/index.js";
3
+ import { OpenResponsesContinuation } from "./open-responses-continuation.js";
3
4
  export interface Options {
4
5
  readonly id: string;
5
6
  readonly name: string;
@@ -7,6 +8,7 @@ export interface Options {
7
8
  readonly enabled?: (url: string) => boolean;
8
9
  readonly url?: (url: string) => string;
9
10
  readonly headers?: (headers: Headers.Headers) => Headers.Headers;
11
+ readonly continuation?: OpenResponsesContinuation.Shape;
10
12
  }
11
13
  export interface Prepared {
12
14
  readonly http: HttpTransport.HttpPrepared<string>;
@@ -10,7 +10,6 @@ const WebSocketResponseCreate = Schema.StructWithRest(Schema.Struct({ type: Sche
10
10
  ]);
11
11
  const decodeMessage = ProviderShared.validateWith(Schema.decodeUnknownEffect(WebSocketResponseCreate));
12
12
  const encodeMessage = Schema.encodeSync(Schema.fromJsonString(WebSocketResponseCreate));
13
- const decodeEvent = Schema.decodeUnknownEffect(OpenResponses.protocol.stream.event);
14
13
  const message = (body) => Effect.gen(function* () {
15
14
  if (!ProviderShared.isRecord(body))
16
15
  return yield* ProviderShared.invalidRequest("Open Responses WebSocket body must be a JSON object");
@@ -28,7 +27,7 @@ const driver = (options, body) => {
28
27
  return { message: body, mode: "full" };
29
28
  }),
30
29
  observe: (_create, frame) => Effect.gen(function* () {
31
- const event = yield* decodeEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(options.id, `Invalid ${options.name} WebSocket event`, frame, cause)));
30
+ const event = yield* OpenResponses.decodeChannelEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(options.id, `Invalid ${options.name} WebSocket event`, frame, cause)));
32
31
  if (terminal)
33
32
  return yield* ProviderShared.eventError(options.id, `${options.name} emitted ${event.type} after a terminal event`, frame);
34
33
  if (event.type === "error") {
@@ -97,6 +96,7 @@ export const transport = (options) => {
97
96
  request: create.request,
98
97
  message: create.message,
99
98
  base,
99
+ continuation: options.continuation,
100
100
  }),
101
101
  };
102
102
  })
@@ -1,12 +1,16 @@
1
1
  import type { WebSocketChannelDriver } from "../route/transport/index.js";
2
+ /**
3
+ * Fields to send next to `previous_response_id` on an incremental step, or undefined to send the step in full.
4
+ * Whether omitted fields carry over from the continued response is provider behavior the route must know.
5
+ */
6
+ export type Shape = (request: Readonly<Record<string, unknown>>) => Readonly<Record<string, unknown>> | undefined;
2
7
  export interface DriverInput {
3
8
  readonly id: string;
4
9
  readonly name: string;
5
10
  readonly request: Readonly<Record<string, unknown>>;
6
11
  readonly message: string;
7
12
  readonly base: WebSocketChannelDriver;
13
+ readonly continuation?: Shape;
8
14
  }
9
15
  export declare const driver: (input: DriverInput) => WebSocketChannelDriver;
10
- export declare const OpenResponsesContinuation: {
11
- readonly driver: (input: DriverInput) => WebSocketChannelDriver;
12
- };
16
+ export * as OpenResponsesContinuation from "./open-responses-continuation.js";
@@ -4,7 +4,6 @@ import * as ProviderShared from "./shared.js";
4
4
  import { OpenResponses } from "./open-responses.js";
5
5
  const PROTOCOL = "open-responses.websocket.v1";
6
6
  const VERSION = 1;
7
- const decodeEvent = Schema.decodeUnknownEffect(OpenResponses.protocol.stream.event);
8
7
  const checkpointValue = (checkpoint) => {
9
8
  if (checkpoint?.protocol !== PROTOCOL || !ProviderShared.isRecord(checkpoint.value))
10
9
  return undefined;
@@ -104,21 +103,24 @@ const rejected = (observation, recovery) => ({
104
103
  });
105
104
  export const driver = (input) => {
106
105
  const { previous_response_id: _previousResponseID, ...request } = input.request;
106
+ const shape = input.continuation ?? ((fields) => fields);
107
107
  let output = [];
108
108
  return {
109
109
  create: (checkpoint) => Effect.sync(() => {
110
110
  output = [];
111
111
  const previous = checkpointValue(checkpoint);
112
- const delta = previous ? incremental(request, previous) : undefined;
113
- if (!previous || !delta)
112
+ // Ask the route first: diffing the whole history is wasted when it declines the continuation.
113
+ const fields = previous ? shape(request) : undefined;
114
+ const delta = previous && fields ? incremental(request, previous) : undefined;
115
+ if (!previous || !fields || !delta)
114
116
  return { message: ProviderShared.encodeJson(request), mode: "full" };
115
117
  return {
116
- message: ProviderShared.encodeJson({ ...request, input: delta, previous_response_id: previous.responseID }),
118
+ message: ProviderShared.encodeJson({ ...fields, input: delta, previous_response_id: previous.responseID }),
117
119
  mode: "incremental",
118
120
  };
119
121
  }),
120
122
  observe: (create, frame) => Effect.gen(function* () {
121
- const event = yield* decodeEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(input.id, `Invalid ${input.name} WebSocket event`, frame, cause)));
123
+ const event = yield* OpenResponses.decodeChannelEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(input.id, `Invalid ${input.name} WebSocket event`, frame, cause)));
122
124
  const observation = yield* input.base.observe(create, frame);
123
125
  if (event.type === "response.output_item.done" && event.item)
124
126
  output.push(event.item);
@@ -165,4 +167,4 @@ export const driver = (input) => {
165
167
  }),
166
168
  };
167
169
  };
168
- export const OpenResponsesContinuation = { driver };
170
+ export * as OpenResponsesContinuation from "./open-responses-continuation.js";
@@ -653,6 +653,78 @@ export type Event = Schema.Schema.Type<typeof Event>;
653
653
  export type NormalizedEvent = Event & {
654
654
  readonly item?: OutputItem | null;
655
655
  };
656
+ /**
657
+ * Decodes one WebSocket frame. xAI answers a rejected `response.create` with `{ "error": { "message", "type" } }` and no
658
+ * event type; that envelope reads as an error event so the failure classifies instead of failing decoding.
659
+ */
660
+ export declare const decodeChannelEvent: (frame: string) => Effect.Effect<{
661
+ readonly [x: string]: unknown;
662
+ readonly type: string;
663
+ readonly status?: unknown;
664
+ readonly code?: string | null | undefined;
665
+ readonly message?: string | undefined;
666
+ readonly headers?: unknown;
667
+ readonly text?: string | undefined;
668
+ readonly error?: {
669
+ readonly type?: string | null | undefined;
670
+ readonly code?: string | null | undefined;
671
+ readonly message?: string | null | undefined;
672
+ readonly param?: string | null | undefined;
673
+ } | null | undefined;
674
+ readonly item?: {
675
+ readonly [x: string]: unknown;
676
+ readonly type: string;
677
+ readonly id?: string | undefined;
678
+ readonly name?: string | undefined;
679
+ readonly namespace?: string | undefined;
680
+ readonly arguments?: string | undefined;
681
+ readonly encrypted_content?: string | null | undefined;
682
+ readonly call_id?: string | undefined;
683
+ } | null | undefined;
684
+ readonly delta?: string | undefined;
685
+ readonly response?: {
686
+ readonly [x: string]: unknown;
687
+ readonly id?: string | undefined;
688
+ readonly output?: readonly {
689
+ readonly [x: string]: unknown;
690
+ readonly type: string;
691
+ readonly id?: string | undefined;
692
+ readonly name?: string | undefined;
693
+ readonly namespace?: string | undefined;
694
+ readonly arguments?: string | undefined;
695
+ readonly encrypted_content?: string | null | undefined;
696
+ readonly call_id?: string | undefined;
697
+ }[] | undefined;
698
+ readonly error?: {
699
+ readonly type?: string | null | undefined;
700
+ readonly code?: string | null | undefined;
701
+ readonly message?: string | null | undefined;
702
+ readonly param?: string | null | undefined;
703
+ } | null | undefined;
704
+ readonly usage?: {
705
+ readonly input_tokens?: number | undefined;
706
+ readonly output_tokens?: number | undefined;
707
+ readonly output_tokens_details?: {
708
+ readonly reasoning_tokens?: number | undefined;
709
+ } | null | undefined;
710
+ readonly total_tokens?: number | undefined;
711
+ readonly input_tokens_details?: {
712
+ readonly cached_tokens?: number | undefined;
713
+ readonly cache_write_tokens?: number | undefined;
714
+ } | null | undefined;
715
+ } | null | undefined;
716
+ readonly service_tier?: string | null | undefined;
717
+ readonly incomplete_details?: {
718
+ readonly reason?: string | undefined;
719
+ } | null | undefined;
720
+ } | undefined;
721
+ readonly arguments?: string | undefined;
722
+ readonly param?: string | null | undefined;
723
+ readonly status_code?: unknown;
724
+ readonly item_id?: string | undefined;
725
+ readonly output_index?: number | undefined;
726
+ readonly summary_index?: number | undefined;
727
+ }, Schema.SchemaError, never>;
656
728
  export interface ProviderAdapter {
657
729
  readonly id: string;
658
730
  readonly name: string;
@@ -288,6 +288,15 @@ export const Event = Schema.StructWithRest(Schema.Struct({
288
288
  status_code: Schema.optional(Schema.Unknown),
289
289
  headers: Schema.optional(Schema.Unknown),
290
290
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
291
+ const decodeEventValue = Schema.decodeUnknownEffect(Event);
292
+ const decodeFrame = Schema.decodeUnknownEffect(ProviderShared.Json);
293
+ /**
294
+ * Decodes one WebSocket frame. xAI answers a rejected `response.create` with `{ "error": { "message", "type" } }` and no
295
+ * event type; that envelope reads as an error event so the failure classifies instead of failing decoding.
296
+ */
297
+ export const decodeChannelEvent = (frame) => decodeFrame(frame).pipe(Effect.flatMap((value) => decodeEventValue(ProviderShared.isRecord(value) && value.type === undefined && ProviderShared.isRecord(value.error)
298
+ ? { ...value, type: "error" }
299
+ : value)));
291
300
  const BASE_ADAPTER = { id: ADAPTER, name: NAME };
292
301
  // =============================================================================
293
302
  // Request Lowering
@@ -20,6 +20,10 @@ const responsesRoute = Route.make({
20
20
  id: "openai-responses",
21
21
  name: "xAI Responses",
22
22
  rotateAfterMs: RESPONSES_WEBSOCKET_ROTATE_AFTER_MS,
23
+ // xAI continues a chain only from stored responses: with `store: false` (the route default) `previous_response_id`
24
+ // fails with "Response with id=… not found", so those steps are sent in full over the reused connection. It also
25
+ // rejects `instructions` next to `previous_response_id` and keeps the instructions of the response it continues.
26
+ continuation: ({ instructions: _instructions, ...request }) => (request.store === false ? undefined : request),
23
27
  }),
24
28
  defaults: { providerOptions: { store: false, include: ["reasoning.encrypted_content"] } },
25
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-dev-19443",
3
+ "version": "0.0.0-dev-19445",
4
4
  "name": "@opencode/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.0.0-alpha.1",
32
32
  "@effect/platform-node": "4.0.0-rc.112",
33
- "@opencode/http-recorder": "0.0.0-dev-19443",
33
+ "@opencode/http-recorder": "0.0.0-dev-19445",
34
34
  "@tsconfig/bun": "1.0.9",
35
35
  "@types/bun": "1.4.0",
36
36
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -40,7 +40,7 @@
40
40
  "@aws-sdk/credential-providers": "3.1057.0",
41
41
  "@smithy/eventstream-codec": "4.2.14",
42
42
  "@smithy/util-utf8": "4.2.2",
43
- "@opencode/schema": "0.0.0-dev-19443",
43
+ "@opencode/schema": "0.0.0-dev-19445",
44
44
  "aws4fetch": "1.0.20",
45
45
  "effect": "4.0.0-rc.112",
46
46
  "google-auth-library": "10.5.0"