opencode-cmd-provider 0.1.0

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 (46) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +172 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +7 -0
  6. package/dist/src/env.d.ts +7 -0
  7. package/dist/src/env.js +24 -0
  8. package/dist/src/plugin/auth-server.d.ts +30 -0
  9. package/dist/src/plugin/auth-server.js +158 -0
  10. package/dist/src/plugin/auth.d.ts +6 -0
  11. package/dist/src/plugin/auth.js +38 -0
  12. package/dist/src/plugin/index.d.ts +6 -0
  13. package/dist/src/plugin/index.js +39 -0
  14. package/dist/src/plugin/models.d.ts +7 -0
  15. package/dist/src/plugin/models.js +50 -0
  16. package/dist/src/provider/aisdk-types.d.ts +9 -0
  17. package/dist/src/provider/aisdk-types.js +1 -0
  18. package/dist/src/provider/auth-key.d.ts +7 -0
  19. package/dist/src/provider/auth-key.js +65 -0
  20. package/dist/src/provider/command-code-model.d.ts +39 -0
  21. package/dist/src/provider/command-code-model.js +425 -0
  22. package/dist/src/provider/converters.d.ts +33 -0
  23. package/dist/src/provider/converters.js +256 -0
  24. package/dist/src/provider/cost.d.ts +19 -0
  25. package/dist/src/provider/cost.js +19 -0
  26. package/dist/src/provider/index.d.ts +5 -0
  27. package/dist/src/provider/index.js +9 -0
  28. package/dist/src/provider/json-schema.d.ts +1 -0
  29. package/dist/src/provider/json-schema.js +374 -0
  30. package/dist/src/provider/modalities.d.ts +9 -0
  31. package/dist/src/provider/modalities.js +53 -0
  32. package/dist/src/provider/models.d.ts +29 -0
  33. package/dist/src/provider/models.js +229 -0
  34. package/dist/src/provider/pricing.d.ts +24 -0
  35. package/dist/src/provider/pricing.js +188 -0
  36. package/dist/src/provider/project-slug.d.ts +1 -0
  37. package/dist/src/provider/project-slug.js +10 -0
  38. package/dist/src/provider/reasoning.d.ts +29 -0
  39. package/dist/src/provider/reasoning.js +74 -0
  40. package/dist/src/provider/redact.d.ts +2 -0
  41. package/dist/src/provider/redact.js +59 -0
  42. package/dist/src/provider/retry.d.ts +8 -0
  43. package/dist/src/provider/retry.js +83 -0
  44. package/dist/src/provider/stream.d.ts +5 -0
  45. package/dist/src/provider/stream.js +105 -0
  46. package/package.json +58 -0
@@ -0,0 +1,65 @@
1
+ // src/provider/auth-key.ts — API-key resolution (PLAN #2 Part A)
2
+ //
3
+ // Port of pi-commandcode-provider `getApiKey` with an `options.apiKey`
4
+ // precedent added (DESIGN §6.2). Precedence:
5
+ // 1. options.apiKey — set by opencode from /connect credentials or config
6
+ // 2. COMMANDCODE_API_KEY environment variable
7
+ // 3. Legacy auth files: ~/.commandcode/auth.json, ~/.omp/agent/auth.json,
8
+ // ~/.pi/agent/auth.json (all three record shapes, malformed files skipped)
9
+ // Returns undefined when no key is found (callers emit the AI SDK error).
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { isRecord, stringValue } from "./converters.js";
14
+ function defaultAuthPaths(home) {
15
+ return [
16
+ join(home, ".commandcode", "auth.json"),
17
+ join(home, ".omp", "agent", "auth.json"),
18
+ join(home, ".pi", "agent", "auth.json"),
19
+ ];
20
+ }
21
+ function apiKeyFromCredentialRecord(value) {
22
+ if (!isRecord(value))
23
+ return undefined;
24
+ const type = stringValue(value.type);
25
+ if (type === "api")
26
+ return stringValue(value.key);
27
+ if (type === "oauth")
28
+ return stringValue(value.access);
29
+ return stringValue(value.key) ?? stringValue(value.access);
30
+ }
31
+ export function resolveApiKey(options = {}) {
32
+ if (options.apiKey)
33
+ return options.apiKey;
34
+ const env = options.env ?? process.env;
35
+ if (env.COMMANDCODE_API_KEY)
36
+ return env.COMMANDCODE_API_KEY;
37
+ const home = options.homeDir?.() ?? homedir();
38
+ const authPaths = options.authPaths ?? defaultAuthPaths(home);
39
+ for (const authPath of authPaths) {
40
+ try {
41
+ if (!existsSync(authPath))
42
+ continue;
43
+ const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
44
+ if (!isRecord(parsed))
45
+ continue;
46
+ // Legacy: direct apiKey or commandcode field.
47
+ const apiKey = stringValue(parsed.apiKey);
48
+ if (apiKey)
49
+ return apiKey;
50
+ const commandcode = stringValue(parsed.commandcode);
51
+ if (commandcode)
52
+ return commandcode;
53
+ // pi stores OAuth credentials as {"commandcode": {"type":"oauth","access":"..."}}.
54
+ // The official Command Code CLI stores API credentials under "command-code".
55
+ const providerKey = apiKeyFromCredentialRecord(parsed.commandcode) ??
56
+ apiKeyFromCredentialRecord(parsed["command-code"]);
57
+ if (providerKey)
58
+ return providerKey;
59
+ }
60
+ catch {
61
+ // Ignore malformed or unreadable auth files.
62
+ }
63
+ }
64
+ return undefined;
65
+ }
@@ -0,0 +1,39 @@
1
+ import type { LanguageModelV3, LanguageModelV3GenerateResult } from "@ai-sdk/provider";
2
+ import type { LanguageModelV3StreamPart, ModelCallOptions } from "./aisdk-types.js";
3
+ export interface CommandCodeModelOptions {
4
+ name?: string;
5
+ baseURL?: string;
6
+ apiKey?: string;
7
+ headers?: Record<string, string>;
8
+ fetch?: typeof fetch;
9
+ timeout?: number;
10
+ maxRetries?: number;
11
+ maxRetryDelayMs?: number;
12
+ authPaths?: readonly string[];
13
+ }
14
+ export declare class CommandCodeLanguageModel implements LanguageModelV3 {
15
+ private readonly options;
16
+ readonly specificationVersion: "v3";
17
+ readonly provider = "commandcode";
18
+ readonly modelId: string;
19
+ readonly supportsStructuredOutputs = false;
20
+ readonly supportsParallelCalls = false;
21
+ readonly supportedUrls: Record<string, RegExp[]>;
22
+ constructor(options: CommandCodeModelOptions, modelId: string);
23
+ private apiBase;
24
+ private costForModel;
25
+ doGenerate(options: ModelCallOptions): Promise<LanguageModelV3GenerateResult>;
26
+ doStream(options: ModelCallOptions): Promise<{
27
+ stream: ReadableStream<LanguageModelV3StreamPart>;
28
+ error?: unknown;
29
+ }>;
30
+ /**
31
+ * Runs one request/parse pass and collects the v3 parts (doGenerate).
32
+ * Stream errors surface as error parts; the first error part is also
33
+ * returned so doGenerate can throw it.
34
+ */
35
+ private runOnce;
36
+ private bodyFor;
37
+ private headersFor;
38
+ private runStream;
39
+ }
@@ -0,0 +1,425 @@
1
+ // src/provider/command-code-model.ts — AI SDK v3 LanguageModel for Command Code
2
+ // (PLAN #8: doStream tracer bullet; doGenerate lands in #9)
3
+ //
4
+ // Port of pi's createStreamCommandCode loop (core.ts:159-741): SSE parse via
5
+ // #3's stream helpers, retry/abort/timeout via retry.ts, redaction on every
6
+ // surfaced error, v3 stream parts on the wire.
7
+ import { randomUUID } from "node:crypto";
8
+ import { resolveApiKey } from "./auth-key.js";
9
+ import { messagesToCC, toolsToJson, systemPromptToText, getEnvironmentInfo, isRecord, } from "./converters.js";
10
+ import { parseStreamEventLine, ccEventToStreamPart } from "./stream.js";
11
+ import { redactCommandCodeErrorText, commandCodeErrorMessage } from "./redact.js";
12
+ import { calculateCommandCodeCost } from "./cost.js";
13
+ import { ZERO_MODEL_COST, MODEL_COSTS } from "./pricing.js";
14
+ import { mappedReasoningEffort, thinkingMetadataForModel, isReasoningModel } from "./reasoning.js";
15
+ import { modelSupportsImageInput } from "./modalities.js";
16
+ import { isRetryableStatus, retryDelayMs, raceAbort, abortError, timeoutError, delay, } from "./retry.js";
17
+ import { projectSlugFromPath } from "./project-slug.js";
18
+ const COMMAND_CODE_CLI_VERSION = "1.15.1";
19
+ const DEFAULT_GENERATE_MAX_TOKENS = 64_000;
20
+ const DEFAULT_MAX_RETRIES = 0;
21
+ const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
22
+ const DEFAULT_BASE_URL = "https://api.commandcode.ai";
23
+ function promptSystem(prompt) {
24
+ const system = prompt.filter((m) => m.role === "system").map((m) => m.content);
25
+ return system.length > 0 ? system.join("\n") : undefined;
26
+ }
27
+ function errorStream(message) {
28
+ return new ReadableStream({
29
+ start(controller) {
30
+ controller.enqueue({ type: "error", error: new Error(message) });
31
+ controller.close();
32
+ },
33
+ });
34
+ }
35
+ export class CommandCodeLanguageModel {
36
+ options;
37
+ specificationVersion = "v3";
38
+ provider = "commandcode";
39
+ modelId;
40
+ supportsStructuredOutputs = false;
41
+ supportsParallelCalls = false;
42
+ supportedUrls = {};
43
+ constructor(options, modelId) {
44
+ this.options = options;
45
+ this.modelId = modelId;
46
+ }
47
+ apiBase() {
48
+ return this.options.baseURL ?? DEFAULT_BASE_URL;
49
+ }
50
+ costForModel() {
51
+ return { cost: MODEL_COSTS[this.modelId] ?? ZERO_MODEL_COST };
52
+ }
53
+ async doGenerate(options) {
54
+ const { parts, error } = await this.runOnce(options);
55
+ if (error)
56
+ throw error;
57
+ const content = [];
58
+ let text = "";
59
+ let reasoning = "";
60
+ let finishReason = { unified: "other", raw: "unknown" };
61
+ let usage;
62
+ for (const part of parts) {
63
+ switch (part.type) {
64
+ case "text-delta":
65
+ text += part.delta;
66
+ break;
67
+ case "reasoning-delta":
68
+ reasoning += part.delta;
69
+ break;
70
+ case "tool-call":
71
+ content.push({
72
+ type: "tool-call",
73
+ toolCallId: part.toolCallId,
74
+ toolName: part.toolName,
75
+ input: part.input,
76
+ });
77
+ break;
78
+ case "finish":
79
+ finishReason = part.finishReason;
80
+ usage = part.usage;
81
+ break;
82
+ case "error":
83
+ throw part.error;
84
+ default:
85
+ break;
86
+ }
87
+ }
88
+ if (text)
89
+ content.push({ type: "text", text });
90
+ if (reasoning)
91
+ content.push({ type: "reasoning", text: reasoning });
92
+ if (!usage) {
93
+ usage = {
94
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
95
+ outputTokens: { total: 0, text: 0, reasoning: 0 },
96
+ };
97
+ }
98
+ return { content, finishReason, usage, warnings: [] };
99
+ }
100
+ async doStream(options) {
101
+ return {
102
+ stream: this.runStream(this.bodyFor(options), this.headersFor(options), options.abortSignal, options),
103
+ };
104
+ }
105
+ /**
106
+ * Runs one request/parse pass and collects the v3 parts (doGenerate).
107
+ * Stream errors surface as error parts; the first error part is also
108
+ * returned so doGenerate can throw it.
109
+ */
110
+ async runOnce(options) {
111
+ const apiKey = resolveApiKey({
112
+ apiKey: this.options.apiKey,
113
+ authPaths: this.options.authPaths,
114
+ });
115
+ if (!apiKey) {
116
+ return {
117
+ parts: [],
118
+ error: new Error("No Command Code API key. Run /connect and select Command Code, set the COMMANDCODE_API_KEY env var, or configure an auth file."),
119
+ };
120
+ }
121
+ const parts = [];
122
+ const stream = this.runStream(this.bodyFor(options), this.headersFor(options), options.abortSignal, options, parts);
123
+ const reader = stream.getReader();
124
+ for (;;) {
125
+ const { done } = await reader.read();
126
+ if (done)
127
+ break;
128
+ }
129
+ await reader.cancel().catch(() => { });
130
+ const errorPart = parts.find((p) => p.type === "error");
131
+ return {
132
+ parts,
133
+ error: errorPart && errorPart.type === "error" ? errorPart.error : undefined,
134
+ };
135
+ }
136
+ bodyFor(options) {
137
+ const reasoningEffort = mappedReasoningEffort({
138
+ reasoning: isReasoningModel(this.modelId),
139
+ thinkingLevelMap: thinkingMetadataForModel(this.modelId)?.thinkingLevelMap,
140
+ }, {
141
+ reasoning: options.providerOptions?.reasoning ??
142
+ options.providerOptions?.reasoningEffort,
143
+ });
144
+ const maxTokens = Math.min(options.maxOutputTokens ?? DEFAULT_GENERATE_MAX_TOKENS, DEFAULT_GENERATE_MAX_TOKENS);
145
+ const allowImages = modelSupportsImageInput(this.modelId);
146
+ return {
147
+ config: {
148
+ workingDir: process.cwd(),
149
+ date: new Date().toISOString().split("T")[0],
150
+ environment: getEnvironmentInfo(),
151
+ structure: [],
152
+ isGitRepo: false,
153
+ currentBranch: "",
154
+ mainBranch: "",
155
+ gitStatus: "",
156
+ recentCommits: [],
157
+ },
158
+ memory: null,
159
+ taste: null,
160
+ skills: null,
161
+ params: {
162
+ model: this.modelId,
163
+ messages: messagesToCC(options.prompt, { allowImages }),
164
+ tools: toolsToJson((options.tools ?? [])
165
+ .filter((tool) => tool.type === "function")
166
+ .map((tool) => ({
167
+ name: tool.name,
168
+ description: tool.description,
169
+ parameters: tool.inputSchema,
170
+ }))),
171
+ system: systemPromptToText(promptSystem(options.prompt)),
172
+ max_tokens: maxTokens,
173
+ temperature: 0.3,
174
+ stream: true,
175
+ ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
176
+ },
177
+ threadId: randomUUID(),
178
+ };
179
+ }
180
+ headersFor(options) {
181
+ const apiKey = resolveApiKey({
182
+ apiKey: this.options.apiKey,
183
+ authPaths: this.options.authPaths,
184
+ });
185
+ return {
186
+ "Content-Type": "application/json",
187
+ Authorization: `Bearer ${apiKey ?? ""}`,
188
+ "x-command-code-version": COMMAND_CODE_CLI_VERSION,
189
+ "x-cli-environment": "production",
190
+ "x-project-slug": projectSlugFromPath(process.cwd()),
191
+ "x-taste-learning": "true",
192
+ "x-co-flag": "false",
193
+ ...this.options.headers,
194
+ ...(options.headers ?? {}),
195
+ };
196
+ }
197
+ runStream(body, headers, signal, options, sink) {
198
+ const timeoutMs = this.options.timeout;
199
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
200
+ const maxRetryDelayMs = this.options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
201
+ const fetchImpl = this.options.fetch ?? fetch;
202
+ const url = `${this.apiBase()}/alpha/generate`;
203
+ const bodyStr = JSON.stringify(body);
204
+ return new ReadableStream({
205
+ start: async (streamController) => {
206
+ const emit = (part) => {
207
+ sink?.push(part);
208
+ streamController.enqueue(part);
209
+ };
210
+ const fail = (error) => {
211
+ const message = error instanceof Error ? error.message : String(error);
212
+ const part = {
213
+ type: "error",
214
+ error: new Error(redactCommandCodeErrorText(message)),
215
+ };
216
+ sink?.push(part);
217
+ streamController.enqueue(part);
218
+ streamController.close();
219
+ };
220
+ const key = resolveApiKey({
221
+ apiKey: this.options.apiKey,
222
+ authPaths: this.options.authPaths,
223
+ });
224
+ if (!key) {
225
+ fail("No Command Code API key. Run /connect and select Command Code, set the COMMANDCODE_API_KEY env var, or configure an auth file.");
226
+ return;
227
+ }
228
+ const handleEvent = (event) => {
229
+ if (!isRecord(event))
230
+ return false;
231
+ try {
232
+ const parts = ccEventToStreamPart(event);
233
+ let finished = false;
234
+ for (const part of parts) {
235
+ if (part.type === "finish") {
236
+ finished = true;
237
+ const usage = part.usage;
238
+ calculateCommandCodeCost(this.costForModel(), {
239
+ input: usage.inputTokens.total ?? 0,
240
+ output: usage.outputTokens.total ?? 0,
241
+ cacheRead: 0,
242
+ cacheWrite: 0,
243
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
244
+ });
245
+ }
246
+ emit(part);
247
+ }
248
+ return finished;
249
+ }
250
+ catch (streamError) {
251
+ fail(streamError);
252
+ return true;
253
+ }
254
+ };
255
+ let reader;
256
+ const controller = new AbortController();
257
+ const onOuterAbort = () => controller.abort();
258
+ try {
259
+ signal?.addEventListener("abort", onOuterAbort, { once: true });
260
+ if (signal?.aborted)
261
+ throw abortError("Aborted");
262
+ let response;
263
+ let finished = false;
264
+ retryLoop: for (let attempt = 0;; attempt++) {
265
+ const attemptController = new AbortController();
266
+ let attemptTimedOut = false;
267
+ let attemptTimeoutId;
268
+ const clearAttemptTimeout = () => {
269
+ if (attemptTimeoutId !== undefined) {
270
+ clearTimeout(attemptTimeoutId);
271
+ attemptTimeoutId = undefined;
272
+ }
273
+ };
274
+ if (timeoutMs !== undefined) {
275
+ attemptTimeoutId = setTimeout(() => {
276
+ attemptTimedOut = true;
277
+ attemptController.abort();
278
+ }, timeoutMs);
279
+ }
280
+ const onOuterAbort2 = () => attemptController.abort();
281
+ controller.signal.addEventListener("abort", onOuterAbort2, { once: true });
282
+ const raceAttempt = (promise) => raceAbort(promise, attemptController.signal).catch((error) => {
283
+ if (attemptTimedOut)
284
+ throw timeoutError(timeoutMs);
285
+ throw error;
286
+ });
287
+ try {
288
+ try {
289
+ response = await fetchImpl(url, {
290
+ method: "POST",
291
+ headers,
292
+ body: bodyStr,
293
+ signal: attemptController.signal,
294
+ });
295
+ }
296
+ catch (fetchError) {
297
+ if (controller.signal.aborted)
298
+ throw abortError("Aborted");
299
+ if (attemptTimedOut) {
300
+ if (attempt < maxRetries)
301
+ continue retryLoop;
302
+ throw timeoutError(timeoutMs);
303
+ }
304
+ throw fetchError;
305
+ }
306
+ // --- HTTP-level retry ---
307
+ if (!response.ok && isRetryableStatus(response.status)) {
308
+ const retryAfter = response.headers.get("retry-after");
309
+ const waitMs = retryDelayMs(attempt, retryAfter, maxRetryDelayMs);
310
+ if (waitMs < 0) {
311
+ throw new Error(`Command Code API error ${response.status}: Retry-After delay exceeds max retry delay`);
312
+ }
313
+ if (attempt < maxRetries) {
314
+ await response.text().catch(() => "");
315
+ if (waitMs > 0)
316
+ await delay(waitMs, controller.signal);
317
+ continue retryLoop;
318
+ }
319
+ }
320
+ if (!response.ok) {
321
+ const errBody = await raceAttempt(response.text().catch(() => ""));
322
+ let errorDetail;
323
+ try {
324
+ const parsedBody = JSON.parse(errBody);
325
+ errorDetail = commandCodeErrorMessage(parsedBody);
326
+ }
327
+ catch {
328
+ // Preserve useful plain-text provider errors only after secret
329
+ // redaction; upstream/proxy bodies may echo credentials.
330
+ }
331
+ const safeBody = redactCommandCodeErrorText(errBody).slice(0, 500);
332
+ const detail = redactCommandCodeErrorText(errorDetail ?? (safeBody || "Provider returned an error"));
333
+ throw new Error(`Command Code API error ${response.status}: ${detail}`);
334
+ }
335
+ // --- Read response stream ---
336
+ reader = response.body?.getReader();
337
+ if (!reader)
338
+ throw new Error("No response body");
339
+ const decoder = new TextDecoder();
340
+ let buffer = "";
341
+ readLoop: for (;;) {
342
+ if (controller.signal.aborted)
343
+ throw abortError("Aborted");
344
+ const { done, value } = await raceAbort(reader.read(), attemptController.signal);
345
+ if (done) {
346
+ if (buffer.trim())
347
+ handleEvent(parseStreamEventLine(buffer));
348
+ break;
349
+ }
350
+ if (controller.signal.aborted)
351
+ throw abortError("Aborted");
352
+ buffer += decoder.decode(value, { stream: true });
353
+ const lines = buffer.split("\n");
354
+ buffer = lines.pop() ?? "";
355
+ for (const line of lines) {
356
+ if (controller.signal.aborted)
357
+ throw abortError("Aborted");
358
+ if (handleEvent(parseStreamEventLine(line))) {
359
+ finished = true;
360
+ break readLoop;
361
+ }
362
+ }
363
+ }
364
+ // Stream completed successfully.
365
+ break retryLoop;
366
+ }
367
+ catch (streamError) {
368
+ // Stream-level error (e.g. API returned 200 OK but sent an error
369
+ // event) or per-attempt timeout during stream reading.
370
+ await reader?.cancel().catch(() => { });
371
+ try {
372
+ reader?.releaseLock();
373
+ }
374
+ catch { }
375
+ reader = undefined;
376
+ if (controller.signal.aborted)
377
+ throw streamError;
378
+ // Never retry after visible content was emitted (including timeout mid-stream).
379
+ const canRetry = !finished && attempt < maxRetries;
380
+ if (canRetry) {
381
+ finished = false;
382
+ const waitMs = attemptTimedOut ? 0 : retryDelayMs(attempt, null, maxRetryDelayMs);
383
+ if (waitMs > 0)
384
+ await delay(waitMs, controller.signal);
385
+ continue retryLoop;
386
+ }
387
+ if (attemptTimedOut)
388
+ throw timeoutError(timeoutMs);
389
+ throw streamError;
390
+ }
391
+ finally {
392
+ controller.signal.removeEventListener("abort", onOuterAbort2);
393
+ clearAttemptTimeout();
394
+ }
395
+ }
396
+ if (!finished) {
397
+ // The server closed the stream without a finish event; the AI SDK
398
+ // expects a finish part to terminate a stream.
399
+ emit({
400
+ type: "finish",
401
+ finishReason: { unified: "stop", raw: "stop" },
402
+ usage: {
403
+ inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
404
+ outputTokens: { total: 0, text: 0, reasoning: 0 },
405
+ },
406
+ });
407
+ }
408
+ streamController.close();
409
+ }
410
+ catch (error) {
411
+ if (controller.signal.aborted) {
412
+ // Outer abort: emit a proper AbortError part (AI SDK contract).
413
+ fail(abortError());
414
+ }
415
+ else {
416
+ fail(error);
417
+ }
418
+ }
419
+ finally {
420
+ signal?.removeEventListener("abort", onOuterAbort);
421
+ }
422
+ },
423
+ });
424
+ }
425
+ }
@@ -0,0 +1,33 @@
1
+ export { toJsonSchema } from "./json-schema.js";
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ export declare function stringValue(value: unknown): string | undefined;
4
+ export declare function recordArray(value: unknown): readonly Record<string, unknown>[];
5
+ export declare function recordOrEmpty(value: unknown): Record<string, unknown>;
6
+ export declare function numberValue(value: unknown): number | undefined;
7
+ export type CCImagePart = {
8
+ type: "image";
9
+ image: string;
10
+ mimeType: string;
11
+ };
12
+ export type CCContentPart = {
13
+ type: "text";
14
+ text: string;
15
+ } | CCImagePart;
16
+ type PromptLike = readonly {
17
+ role?: unknown;
18
+ content?: unknown;
19
+ }[];
20
+ export declare function assertTextOnlyMessages(messages?: PromptLike): void;
21
+ export declare function textContent(message: {
22
+ content?: unknown;
23
+ }): string;
24
+ export declare function getEnvironmentInfo(): string;
25
+ export interface SdkTool {
26
+ description?: string;
27
+ parameters: unknown;
28
+ }
29
+ export declare function toolsToJson(tools?: Record<string, SdkTool> | readonly SdkTool[]): unknown[];
30
+ export declare function messagesToCC(messages: PromptLike, options?: {
31
+ allowImages?: boolean;
32
+ }): unknown[];
33
+ export declare function systemPromptToText(value: unknown): string;