@opencode/ai 0.0.0-beta-19288 → 0.0.0-beta-19296

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.
package/README.md CHANGED
@@ -29,6 +29,134 @@ await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
29
29
 
30
30
  Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
31
31
 
32
+ ## Z.AI
33
+
34
+ `ZAI` uses the standard API. Chat Completions is the default language-model API;
35
+ the existing `.image(...)` selector provides image generation.
36
+
37
+ ```ts
38
+ import { LLM } from "@opencode/ai"
39
+ import { ZAI, ZAICodingPlan } from "@opencode/ai/providers"
40
+
41
+ const zai = ZAI.configure({ apiKey: process.env.ZAI_API_KEY })
42
+ const request = LLM.request({
43
+ model: zai.model("glm-5.3"), // also zai.chat("glm-5.3")
44
+ prompt: "Explain this design.",
45
+ providerOptions: {
46
+ reasoningEffort: "high",
47
+ thinking: { type: "enabled", clear_thinking: false },
48
+ },
49
+ })
50
+
51
+ const coding = ZAICodingPlan.configure({ apiKey: process.env.ZAI_API_KEY })
52
+ const messages = LLM.request({
53
+ model: coding.messages("glm-5.3"),
54
+ prompt: "Explain this design.",
55
+ providerOptions: { effort: "high" },
56
+ })
57
+ ```
58
+
59
+ The products have distinct provider identities and endpoints:
60
+
61
+ | Provider | Selector | Default base URL |
62
+ | ----------------------------------- | --------------------------- | ------------------------------------- |
63
+ | `ZAI` (`zai`) | `.model`, `.chat`, `.image` | `https://api.z.ai/api/paas/v4` |
64
+ | `ZAICodingPlan` (`zai-coding-plan`) | `.model`, `.chat` | `https://api.z.ai/api/coding/paas/v4` |
65
+ | `ZAICodingPlan` | `.messages` | `https://api.z.ai/api/anthropic/v1` |
66
+ | `ZAICodingPlan` | `.responses` | `https://api.z.ai/api/v1` |
67
+
68
+ Both read `ZAI_API_KEY` when `apiKey` is omitted and support an explicit `auth` override.
69
+ Coding Plan requires an active subscription. `baseURL` overrides the selected API's
70
+ complete base, including its version prefix. Language-model routes use HTTP/SSE.
71
+
72
+ Options retain the selected API's native semantics:
73
+
74
+ - Chat `reasoningEffort` lowers to `reasoning_effort`; Responses lowers it to `reasoning.effort`.
75
+ Messages `effort` lowers to `output_config.effort`. Omission preserves provider defaults.
76
+ - Chat `thinking` passes `type` and `clear_thinking` through unchanged. Set
77
+ `clear_thinking: false` and replay complete `response.message` values to preserve reasoning
78
+ across user messages and tool loops. The standard API defaults to clearing historical thinking;
79
+ Coding Plan documents preservation by default.
80
+ - Messages accepts `thinking: { type: "enabled" | "adaptive" | "disabled" }` without requiring
81
+ an Anthropic token budget. Coding Plan documents a disabled toggle as low-effort thinking
82
+ for GLM-5.3, with explicit effort taking precedence.
83
+ - Chat also offers `toolStream`, `doSample`, `responseFormat`, `requestID`, and `userID`.
84
+ Tool-argument streaming is enabled when tools are present on GLM-4.6/4.7/5.x;
85
+ `toolStream: false` explicitly disables it. Older model families omit the opt-in.
86
+ - Effort and thinking values remain forward-compatible strings. Their meaning is model-specific:
87
+ GLM-5.3 accepts `low`, `high`, and `max` effort and rejects disabled thinking with HTTP 400;
88
+ the direct GLM-5.2 recordings returned reasoning even with `none` and `minimal` effort,
89
+ whereas explicit `thinking.type: "disabled"` disabled it on GLM-5.2 and GLM-4.7.
90
+
91
+ Standard API recordings cover GLM-5.3 efforts and a full preserved-reasoning tool loop with
92
+ a subsequent user follow-up, GLM-5.2 efforts, older-model thinking toggles, GLM-4.5 tool calls,
93
+ GLM-5.3-Flash image input, and JSON output. Coding Plan has unit coverage for routing,
94
+ request options, and reasoning replay; successful live recordings are pending.
95
+
96
+ Package entrypoints are `@opencode/ai/providers/zai`, `zai/chat`, `zai-coding-plan`,
97
+ `zai-coding-plan/chat`, `zai-coding-plan/messages`, and `zai-coding-plan/responses`.
98
+
99
+ ## Moonshot
100
+
101
+ Moonshot defaults to Chat Completions, with Messages and Responses selectors for Kimi K3:
102
+
103
+ ```ts
104
+ import { LLM } from "@opencode/ai"
105
+ import { Moonshot } from "@opencode/ai/providers"
106
+
107
+ const moonshot = Moonshot.configure({ apiKey: process.env.MOONSHOT_API_KEY })
108
+
109
+ const request = LLM.request({
110
+ model: moonshot.model("kimi-k3"), // also moonshot.chat("kimi-k3")
111
+ prompt: "Explain the tradeoffs in this design.",
112
+ providerOptions: { reasoningEffort: "high" },
113
+ })
114
+
115
+ const messages = LLM.request({
116
+ model: moonshot.messages("kimi-k3"),
117
+ prompt: "Explain the tradeoffs in this design.",
118
+ providerOptions: { effort: "high" },
119
+ })
120
+
121
+ const responses = LLM.request({
122
+ model: moonshot.responses("kimi-k3"),
123
+ prompt: "Explain the tradeoffs in this design.",
124
+ providerOptions: { reasoningEffort: "high" },
125
+ })
126
+ ```
127
+
128
+ When `apiKey` is omitted, authentication reads `MOONSHOT_API_KEY`, then `MOONSHOTAI_API_KEY`.
129
+ Chat and Responses use `https://api.moonshot.ai/v1`; Messages uses
130
+ `https://api.moonshot.ai/anthropic/v1`. `baseURL` overrides the selected API's complete base,
131
+ including the version prefix, for regional endpoints or gateways. Each endpoint requires its own valid credentials.
132
+ All three routes use HTTP/SSE.
133
+
134
+ Reasoning options stay native to the selected API and model:
135
+
136
+ | Model/API | Provider options |
137
+ | --------------------------- | --------------------------------------------------------------------------------------- |
138
+ | K3 Chat / Responses | `reasoningEffort: "low" \| "high" \| "max"`; default is `max` |
139
+ | K3 Messages | `effort: "low" \| "high" \| "max"`; default is `max` |
140
+ | K2.6 Chat | `thinking: { type: "enabled" \| "disabled", keep?: "all" \| null }`; default is enabled |
141
+ | K2.7 Code / high-speed Chat | Omit `thinking` to use always-on, preserved reasoning |
142
+
143
+ Omitting options preserves the model's defaults. K3 uses effort rather than the K2.x `thinking`
144
+ parameter. Known effort values have autocomplete while future strings remain accepted.
145
+ For K2.6, `thinking.keep: "all"` enables preservation of reasoning across user messages.
146
+ K3 and both K2.7 Code variants always preserve reasoning. Continue with the returned
147
+ `response.message` and matching tool results so reasoning content and any Messages signatures are retained.
148
+ Leave sampling options such as `temperature` unset to use these models' fixed defaults.
149
+
150
+ The recorded suite covers all three K3 APIs, default and explicit efforts, K2.6 thinking modes,
151
+ both K2.7 Code variants, generated tool loops with a subsequent user follow-up, required/disabled
152
+ tool choice, image-byte input, and native structured output through `http.body` overlays.
153
+ K3 Chat and Messages accept required and disabled tool choice. Responses supports automatic tool
154
+ choice only; explicit `required` and `none` produce a provider `InvalidRequest` error, also covered by recordings.
155
+ The provider targets the Moonshot Open Platform; Kimi Code is a separate product and endpoint.
156
+
157
+ Package entrypoints are `@opencode/ai/providers/moonshot`, `moonshot/chat`, `moonshot/messages`,
158
+ and `moonshot/responses`; each exports `model(modelID, settings)`.
159
+
32
160
  ## MiniMax
33
161
 
34
162
  MiniMax defaults to its Messages API and reads `MINIMAX_API_KEY` when `apiKey` is omitted:
@@ -0,0 +1,202 @@
1
+ import { Protocol } from "../route/protocol.js";
2
+ import { OpenAIChat } from "./openai-chat.js";
3
+ export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {});
4
+ export type OptionsInput = {
5
+ readonly reasoningEffort?: ReasoningEffort;
6
+ readonly thinking?: {
7
+ readonly type?: "enabled" | "disabled" | (string & {});
8
+ /** False retains historical reasoning; omission preserves the endpoint's default. */
9
+ readonly clear_thinking?: boolean;
10
+ };
11
+ readonly toolStream?: boolean;
12
+ readonly doSample?: boolean;
13
+ readonly responseFormat?: {
14
+ readonly type: "text" | "json_object" | (string & {});
15
+ };
16
+ readonly requestID?: string;
17
+ readonly userID?: string;
18
+ };
19
+ export declare const compatibility: {
20
+ maxTokensField: "max_tokens";
21
+ supportsStore: false;
22
+ supportsStrictMode: false;
23
+ reasoningField: string;
24
+ zaiToolStream: false;
25
+ };
26
+ export declare const protocol: Protocol<{
27
+ readonly model: string;
28
+ readonly messages: readonly ({
29
+ readonly role: "system";
30
+ readonly content: string | readonly ({
31
+ readonly type: "text";
32
+ readonly text: string;
33
+ readonly cache_control?: {
34
+ readonly type: "ephemeral";
35
+ readonly ttl?: string | undefined;
36
+ } | undefined;
37
+ } | {
38
+ readonly type: "image_url";
39
+ readonly image_url: {
40
+ readonly url: string;
41
+ };
42
+ })[];
43
+ } | {
44
+ readonly role: "user";
45
+ readonly content: string | readonly ({
46
+ readonly type: "text";
47
+ readonly text: string;
48
+ readonly cache_control?: {
49
+ readonly type: "ephemeral";
50
+ readonly ttl?: string | undefined;
51
+ } | undefined;
52
+ } | {
53
+ readonly type: "image_url";
54
+ readonly image_url: {
55
+ readonly url: string;
56
+ };
57
+ })[];
58
+ } | {
59
+ readonly [x: string]: unknown;
60
+ readonly content: string | null;
61
+ readonly role: "assistant";
62
+ readonly reasoning?: string | undefined;
63
+ readonly reasoning_content?: string | undefined;
64
+ readonly reasoning_text?: string | undefined;
65
+ readonly cache_control?: {
66
+ readonly type: "ephemeral";
67
+ readonly ttl?: string | undefined;
68
+ } | undefined;
69
+ readonly tool_calls?: readonly {
70
+ readonly id: string;
71
+ readonly type: "function";
72
+ readonly function: {
73
+ readonly name: string;
74
+ readonly arguments: string;
75
+ };
76
+ }[] | undefined;
77
+ readonly reasoning_details?: unknown;
78
+ } | {
79
+ readonly content: string;
80
+ readonly role: "tool";
81
+ readonly tool_call_id: string;
82
+ readonly cache_control?: {
83
+ readonly type: "ephemeral";
84
+ readonly ttl?: string | undefined;
85
+ } | undefined;
86
+ })[];
87
+ readonly stream: true;
88
+ readonly max_completion_tokens?: number | undefined;
89
+ readonly max_tokens?: number | undefined;
90
+ readonly tools?: readonly {
91
+ readonly function: {
92
+ readonly description: string;
93
+ readonly name: string;
94
+ readonly parameters: {
95
+ readonly [x: string]: unknown;
96
+ };
97
+ readonly strict?: boolean | undefined;
98
+ };
99
+ readonly type: "function";
100
+ readonly cache_control?: {
101
+ readonly type: "ephemeral";
102
+ readonly ttl?: string | undefined;
103
+ } | undefined;
104
+ }[] | undefined;
105
+ readonly stop?: readonly string[] | undefined;
106
+ readonly temperature?: number | undefined;
107
+ readonly seed?: number | undefined;
108
+ readonly thinking?: {
109
+ readonly type?: string | undefined;
110
+ readonly clear_thinking?: boolean | undefined;
111
+ } | undefined;
112
+ readonly user_id?: string | undefined;
113
+ readonly tool_choice?: "required" | "auto" | "none" | {
114
+ readonly type: "function";
115
+ readonly function: {
116
+ readonly name: string;
117
+ };
118
+ } | undefined;
119
+ readonly top_p?: number | undefined;
120
+ readonly frequency_penalty?: number | undefined;
121
+ readonly presence_penalty?: number | undefined;
122
+ readonly prompt_cache_key?: string | undefined;
123
+ readonly reasoning_effort?: import("./utils/open-responses-options.js").ReasoningEffort | undefined;
124
+ readonly store?: boolean | undefined;
125
+ readonly stream_options?: {
126
+ readonly include_usage: boolean;
127
+ } | undefined;
128
+ readonly tool_stream?: boolean | undefined;
129
+ readonly response_format?: {
130
+ readonly type: string;
131
+ } | undefined;
132
+ readonly do_sample?: boolean | undefined;
133
+ readonly request_id?: string | undefined;
134
+ }, string, "[DONE]" | {
135
+ readonly [x: string]: unknown;
136
+ readonly error?: {
137
+ readonly [x: string]: unknown;
138
+ readonly message: string;
139
+ readonly code?: string | number | null | undefined;
140
+ } | null | undefined;
141
+ readonly usage?: {
142
+ readonly [x: string]: unknown;
143
+ readonly cached_tokens?: number | null | undefined;
144
+ readonly prompt_tokens?: number | null | undefined;
145
+ readonly completion_tokens?: number | null | undefined;
146
+ readonly total_tokens?: number | null | undefined;
147
+ readonly prompt_tokens_details?: {
148
+ readonly [x: string]: unknown;
149
+ readonly cached_tokens?: number | null | undefined;
150
+ readonly cache_write_tokens?: number | null | undefined;
151
+ } | null | undefined;
152
+ readonly prompt_cache_hit_tokens?: number | null | undefined;
153
+ readonly completion_tokens_details?: {
154
+ readonly [x: string]: unknown;
155
+ readonly reasoning_tokens?: number | null | undefined;
156
+ readonly accepted_prediction_tokens?: number | null | undefined;
157
+ readonly rejected_prediction_tokens?: number | null | undefined;
158
+ } | null | undefined;
159
+ } | null | undefined;
160
+ readonly choices?: readonly {
161
+ readonly [x: string]: unknown;
162
+ readonly delta?: {
163
+ readonly [x: string]: unknown;
164
+ readonly reasoning?: string | null | undefined;
165
+ readonly reasoning_content?: string | null | undefined;
166
+ readonly reasoning_text?: string | null | undefined;
167
+ readonly content?: string | null | undefined;
168
+ readonly refusal?: string | null | undefined;
169
+ readonly tool_calls?: readonly {
170
+ readonly function?: {
171
+ readonly name?: string | null | undefined;
172
+ readonly arguments?: string | null | undefined;
173
+ } | null | undefined;
174
+ readonly id?: string | null | undefined;
175
+ readonly index?: number | null | undefined;
176
+ }[] | null | undefined;
177
+ readonly reasoning_details?: unknown;
178
+ } | null | undefined;
179
+ readonly usage?: {
180
+ readonly [x: string]: unknown;
181
+ readonly cached_tokens?: number | null | undefined;
182
+ readonly prompt_tokens?: number | null | undefined;
183
+ readonly completion_tokens?: number | null | undefined;
184
+ readonly total_tokens?: number | null | undefined;
185
+ readonly prompt_tokens_details?: {
186
+ readonly [x: string]: unknown;
187
+ readonly cached_tokens?: number | null | undefined;
188
+ readonly cache_write_tokens?: number | null | undefined;
189
+ } | null | undefined;
190
+ readonly prompt_cache_hit_tokens?: number | null | undefined;
191
+ readonly completion_tokens_details?: {
192
+ readonly [x: string]: unknown;
193
+ readonly reasoning_tokens?: number | null | undefined;
194
+ readonly accepted_prediction_tokens?: number | null | undefined;
195
+ readonly rejected_prediction_tokens?: number | null | undefined;
196
+ } | null | undefined;
197
+ } | null | undefined;
198
+ readonly finish_reason?: string | null | undefined;
199
+ readonly native_finish_reason?: string | null | undefined;
200
+ }[] | null | undefined;
201
+ }, OpenAIChat.ParserState>;
202
+ export * as ZAIChat from "./zai-chat.js";
@@ -0,0 +1,49 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { Protocol } from "../route/protocol.js";
3
+ import { OpenAIChat } from "./openai-chat.js";
4
+ import { ProviderShared } from "./shared.js";
5
+ const Options = Schema.Struct({
6
+ reasoningEffort: Schema.optional(Schema.String),
7
+ thinking: Schema.optional(Schema.Struct({ type: Schema.optional(Schema.String), clear_thinking: Schema.optional(Schema.Boolean) })),
8
+ toolStream: Schema.optional(Schema.Boolean),
9
+ doSample: Schema.optional(Schema.Boolean),
10
+ responseFormat: Schema.optional(Schema.Struct({ type: Schema.String })),
11
+ requestID: Schema.optional(Schema.String),
12
+ userID: Schema.optional(Schema.String),
13
+ });
14
+ const Body = Schema.Struct({
15
+ ...OpenAIChat.bodyFields,
16
+ thinking: Options.fields.thinking,
17
+ do_sample: Options.fields.doSample,
18
+ response_format: Options.fields.responseFormat,
19
+ request_id: Options.fields.requestID,
20
+ user_id: Options.fields.userID,
21
+ });
22
+ const fromRequest = Effect.fn("ZAIChat.fromRequest")(function* (request) {
23
+ const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {});
24
+ const body = yield* OpenAIChat.protocol.body.from(request);
25
+ return {
26
+ ...body,
27
+ thinking: options.thinking,
28
+ // Tool streaming was introduced in GLM-4.6; older models must not receive the opt-in.
29
+ tool_stream: options.toolStream ??
30
+ (body.tools?.length && /^glm-(?:4\.[67]|5(?:[.-]|$))/i.test(request.model.id) ? true : undefined),
31
+ do_sample: options.doSample,
32
+ response_format: options.responseFormat,
33
+ request_id: options.requestID,
34
+ user_id: options.userID,
35
+ };
36
+ });
37
+ export const compatibility = {
38
+ maxTokensField: "max_tokens",
39
+ supportsStore: false,
40
+ supportsStrictMode: false,
41
+ reasoningField: "reasoning_content",
42
+ zaiToolStream: false,
43
+ };
44
+ export const protocol = Protocol.make({
45
+ id: "zai-chat",
46
+ body: { schema: Body, from: fromRequest },
47
+ stream: OpenAIChat.protocol.stream,
48
+ });
49
+ export * as ZAIChat from "./zai-chat.js";