opencode-cmd-provider 1.2.2 → 1.4.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.
@@ -31,3 +31,21 @@ export declare function messagesToCC(messages: PromptLike, options?: {
31
31
  allowImages?: boolean;
32
32
  }): unknown[];
33
33
  export declare function systemPromptToText(value: unknown): string;
34
+ declare function openAITools(tools: unknown): unknown[] | undefined;
35
+ declare function anthropicTools(tools: unknown): unknown[] | undefined;
36
+ export interface ProviderRequestOptions {
37
+ prompt: PromptLike;
38
+ model?: string;
39
+ maxOutputTokens?: number;
40
+ providerOptions?: unknown;
41
+ tools?: unknown;
42
+ allowImages?: boolean;
43
+ systemPrompt?: unknown;
44
+ }
45
+ export declare function messagesToOpenAI(prompt: PromptLike, options?: Omit<ProviderRequestOptions, "prompt"> & {
46
+ prompt?: PromptLike;
47
+ }): Record<string, unknown>;
48
+ export declare function messagesToAnthropic(prompt: PromptLike, options?: Omit<ProviderRequestOptions, "prompt"> & {
49
+ prompt?: PromptLike;
50
+ }): Record<string, unknown>;
51
+ export { openAITools, anthropicTools };
@@ -1,5 +1,10 @@
1
1
  // src/provider/converters.ts — AI SDK v3 messages → Command Code payload (PLAN #2 Part B)
2
2
  //
3
+ // Provider API shapes are documented at https://commandcode.ai/docs/provider:
4
+ // - POST /provider/v1/chat/completions follows OpenAI Chat Completions schema
5
+ // - POST /provider/v1/messages follows Anthropic Messages schema
6
+ // - Text + images only; audio/file/document rejected by schema (FAQ)
7
+ //
3
8
  // Port of pi-commandcode-provider/src/converters.ts. Input is the AI SDK v3
4
9
  // prompt format (LanguageModelV3Message / LanguageModelV3Prompt):
5
10
  //
@@ -13,7 +18,9 @@
13
18
  // shapes. `getApiKey` lives in ./auth-key.ts; `parseStreamEventLine` and
14
19
  // `mapFinishReason` are issue #3.
15
20
  import { toJsonSchema } from "./json-schema.js";
21
+ import { isReasoningModel, mappedReasoningEffort, resolveProviderReasoning, thinkingMetadataForModel, } from "./reasoning.js";
16
22
  export { toJsonSchema } from "./json-schema.js";
23
+ const DEFAULT_PROVIDER_MAX_TOKENS = 64_000;
17
24
  export function isRecord(value) {
18
25
  return typeof value === "object" && value !== null && !Array.isArray(value);
19
26
  }
@@ -45,13 +52,35 @@ export function numberValue(value) {
45
52
  }
46
53
  function imageParts(value) {
47
54
  if (isRecord(value))
48
- return value.type === "image" || value.type === "file" ? [value] : [];
49
- return recordArray(value).filter((part) => part.type === "image" ||
50
- (part.type === "file" && stringValue(part.mediaType)?.startsWith("image/")));
55
+ return isImageFilePart(value) ? [value] : [];
56
+ return recordArray(value).filter((part) => isImageFilePart(part));
51
57
  }
52
58
  function imageContentError(role) {
53
59
  return new Error(`Selected Command Code model does not support image content in ${role}`);
54
60
  }
61
+ function nonImageFileError(part, role) {
62
+ const mime = stringValue(part.mediaType) ?? stringValue(part.mimeType) ?? "unknown type";
63
+ return new Error(`Selected Command Code model only accepts text and images, but ${role} contain a non-image file (${mime}). Remove the attachment or convert it to an image.`);
64
+ }
65
+ function hasImageMediaType(part) {
66
+ const mime = stringValue(part.mediaType) ?? stringValue(part.mimeType);
67
+ // A `file` part must declare an image/* media type to be forwarded as an
68
+ // image; a `file` with no verifiable image type (and any non-image type) is
69
+ // rejected, since the Provider API accepts text + images only.
70
+ return mime !== undefined && mime.toLowerCase().startsWith("image/");
71
+ }
72
+ /** True for an `image` part or a `file` part carrying an image/* media type
73
+ * (mediaType or mimeType). Shared by the rejection guard and the content
74
+ * encoders so the image classification is consistent everywhere. */
75
+ function isImageFilePart(part) {
76
+ return part.type === "image" || (part.type === "file" && hasImageMediaType(part));
77
+ }
78
+ function assertProviderContentParts(parts, role) {
79
+ for (const part of parts) {
80
+ if (part.type === "file" && !hasImageMediaType(part))
81
+ throw nonImageFileError(part, role);
82
+ }
83
+ }
55
84
  export function assertTextOnlyMessages(messages) {
56
85
  for (const message of messages ?? []) {
57
86
  if (imageParts(message.content).length > 0) {
@@ -274,3 +303,435 @@ export function systemPromptToText(value) {
274
303
  .join("\n\n");
275
304
  return promptPartToText(value, 0);
276
305
  }
306
+ function promptSystemText(prompt) {
307
+ const system = prompt.filter((m) => m.role === "system").map((m) => m.content);
308
+ return systemPromptToText(system.length > 0 ? system.join("\n") : undefined);
309
+ }
310
+ function cappedMaxTokens(value) {
311
+ const n = typeof value === "number" && Number.isFinite(value) ? value : DEFAULT_PROVIDER_MAX_TOKENS;
312
+ return Math.min(n, DEFAULT_PROVIDER_MAX_TOKENS);
313
+ }
314
+ function reasoningEffortFor(providerOptions, modelId) {
315
+ try {
316
+ const effort = mappedReasoningEffort({
317
+ reasoning: modelId ? isReasoningModel(modelId) : true,
318
+ thinkingLevelMap: modelId ? thinkingMetadataForModel(modelId)?.thinkingLevelMap : undefined,
319
+ }, { reasoning: resolveProviderReasoning(providerOptions, "commandcode") });
320
+ return effort;
321
+ }
322
+ catch {
323
+ return undefined;
324
+ }
325
+ }
326
+ function dataToBase64(data) {
327
+ if (typeof data === "string")
328
+ return data;
329
+ if (data instanceof Uint8Array)
330
+ return Buffer.from(data).toString("base64");
331
+ if (ArrayBuffer.isView(data))
332
+ return Buffer.from(data).toString("base64");
333
+ if (data instanceof ArrayBuffer)
334
+ return Buffer.from(new Uint8Array(data)).toString("base64");
335
+ return undefined;
336
+ }
337
+ function toDataUrl(data, mediaType) {
338
+ const mime = stringValue(mediaType) ?? "application/octet-stream";
339
+ const raw = dataToBase64(data);
340
+ if (!raw || raw.length === 0)
341
+ return undefined;
342
+ if (raw.startsWith("data:"))
343
+ return raw;
344
+ return `data:${mime};base64,${raw}`;
345
+ }
346
+ function filePartToOpenAIImageUrl(part) {
347
+ const url = toDataUrl(part.data ?? part.image, part.mediaType ?? part.mimeType);
348
+ if (!url)
349
+ return undefined;
350
+ return { type: "image_url", image_url: { url } };
351
+ }
352
+ function filePartToAnthropicImage(part) {
353
+ const mime = stringValue(part.mediaType) ?? stringValue(part.mimeType) ?? "image/png";
354
+ let b64;
355
+ const raw = part.data ?? part.image;
356
+ if (typeof raw === "string") {
357
+ if (raw.startsWith("data:")) {
358
+ const after = raw.split(";base64,")[1];
359
+ b64 = after ?? raw;
360
+ const mimeFromUrl = raw.slice(5).split(";")[0];
361
+ if (mimeFromUrl) {
362
+ // prefer mime from data URL if present
363
+ return { type: "image", source: { type: "base64", media_type: mimeFromUrl, data: b64 } };
364
+ }
365
+ }
366
+ else {
367
+ b64 = raw;
368
+ }
369
+ }
370
+ else {
371
+ b64 = dataToBase64(raw);
372
+ }
373
+ if (!b64)
374
+ return undefined;
375
+ return { type: "image", source: { type: "base64", media_type: mime, data: b64 } };
376
+ }
377
+ function textFromPart(part) {
378
+ return stringValue(part.text) ?? "";
379
+ }
380
+ function openAIUserContent(content, allowImages) {
381
+ if (typeof content === "string")
382
+ return content;
383
+ const parts = recordArray(content);
384
+ assertProviderContentParts(parts, "user messages");
385
+ const hasImages = parts.some(isImageFilePart);
386
+ if (!hasImages) {
387
+ const texts = parts.filter((p) => p.type === "text").map(textFromPart);
388
+ if (parts.every((p) => p.type === "text"))
389
+ return texts.join("\n");
390
+ const out = [];
391
+ for (const p of parts) {
392
+ if (p.type === "text")
393
+ out.push({ type: "text", text: textFromPart(p) });
394
+ else if (isImageFilePart(p)) {
395
+ if (!allowImages)
396
+ throw imageContentError("user messages");
397
+ const img = filePartToOpenAIImageUrl(p);
398
+ if (img)
399
+ out.push(img);
400
+ }
401
+ }
402
+ return out;
403
+ }
404
+ if (!allowImages)
405
+ throw imageContentError("user messages");
406
+ const out = [];
407
+ for (const p of parts) {
408
+ if (p.type === "text")
409
+ out.push({ type: "text", text: textFromPart(p) });
410
+ else if (isImageFilePart(p)) {
411
+ const img = filePartToOpenAIImageUrl(p);
412
+ if (img)
413
+ out.push(img);
414
+ }
415
+ }
416
+ return out;
417
+ }
418
+ function anthropicUserContent(content, allowImages) {
419
+ if (typeof content === "string")
420
+ return content;
421
+ const parts = recordArray(content);
422
+ assertProviderContentParts(parts, "user messages");
423
+ const hasImages = parts.some(isImageFilePart);
424
+ // Anthropic docs show string content for simple text: "Count to 5."
425
+ // Return string for single text-only part to match documented shape, array otherwise
426
+ if (!hasImages && parts.length === 1 && parts[0]?.type === "text") {
427
+ return textFromPart(parts[0]);
428
+ }
429
+ if (!hasImages && parts.every((p) => p.type === "text")) {
430
+ // Join multiple text parts with newline — provider accepts string for simple cases
431
+ const texts = parts.map((p) => textFromPart(p)).filter(Boolean);
432
+ if (texts.length === 1)
433
+ return texts[0];
434
+ // For multiple texts, keep array form to preserve structure
435
+ }
436
+ const out = [];
437
+ for (const p of parts) {
438
+ if (p.type === "text")
439
+ out.push({ type: "text", text: textFromPart(p) });
440
+ else if (isImageFilePart(p)) {
441
+ if (!allowImages)
442
+ throw imageContentError("user messages");
443
+ const img = filePartToAnthropicImage(p);
444
+ if (img)
445
+ out.push(img);
446
+ }
447
+ }
448
+ return out;
449
+ }
450
+ function openAITools(tools) {
451
+ if (!tools)
452
+ return undefined;
453
+ let entries;
454
+ if (Array.isArray(tools)) {
455
+ entries = tools.map((t) => [
456
+ stringValue(t.name) ?? "",
457
+ t,
458
+ ]);
459
+ }
460
+ else if (isRecord(tools)) {
461
+ entries = Object.entries(tools);
462
+ }
463
+ else {
464
+ return undefined;
465
+ }
466
+ if (entries.length === 0)
467
+ return undefined;
468
+ return entries.map(([name, tool]) => {
469
+ const rec = isRecord(tool) ? tool : {};
470
+ const description = stringValue(rec.description);
471
+ const parameters = rec.parameters ?? rec.inputSchema ?? {};
472
+ return {
473
+ type: "function",
474
+ function: {
475
+ name,
476
+ description,
477
+ parameters: toJsonSchema(parameters),
478
+ },
479
+ };
480
+ });
481
+ }
482
+ function anthropicTools(tools) {
483
+ if (!tools)
484
+ return undefined;
485
+ let entries;
486
+ if (Array.isArray(tools)) {
487
+ entries = tools.map((t) => [
488
+ stringValue(t.name) ?? "",
489
+ t,
490
+ ]);
491
+ }
492
+ else if (isRecord(tools)) {
493
+ entries = Object.entries(tools);
494
+ }
495
+ else {
496
+ return undefined;
497
+ }
498
+ if (entries.length === 0)
499
+ return undefined;
500
+ return entries.map(([name, tool]) => {
501
+ const rec = isRecord(tool) ? tool : {};
502
+ const description = stringValue(rec.description);
503
+ const parameters = rec.parameters ?? rec.inputSchema ?? {};
504
+ return {
505
+ name,
506
+ description,
507
+ input_schema: toJsonSchema(parameters),
508
+ };
509
+ });
510
+ }
511
+ function promptToOpenAIMessages(prompt, allowImages) {
512
+ const out = [];
513
+ const system = promptSystemText(prompt);
514
+ if (system)
515
+ out.push({ role: "system", content: system });
516
+ const pairedIds = completeToolCallIds(prompt);
517
+ for (const message of prompt) {
518
+ if (message.role === "system")
519
+ continue;
520
+ if (message.role === "user") {
521
+ out.push({ role: "user", content: openAIUserContent(message.content, allowImages) });
522
+ }
523
+ else if (message.role === "assistant") {
524
+ const parts = recordArray(message.content);
525
+ const toolCalls = parts
526
+ .filter((p) => p.type === "tool-call")
527
+ .filter((p) => {
528
+ const id = stringValue(p.toolCallId);
529
+ return id ? pairedIds.has(id) : false;
530
+ });
531
+ const texts = parts
532
+ .filter((p) => p.type === "text")
533
+ .map(textFromPart)
534
+ .filter(Boolean);
535
+ if (toolCalls.length > 0) {
536
+ const content = texts.length > 0 ? texts.join("\n") : null;
537
+ out.push({
538
+ role: "assistant",
539
+ content,
540
+ tool_calls: toolCalls.map((p) => ({
541
+ id: stringValue(p.toolCallId) ?? "",
542
+ type: "function",
543
+ function: {
544
+ name: stringValue(p.toolName) ?? "",
545
+ arguments: JSON.stringify(recordOrEmpty(p.input ?? p.args ?? p.arguments)),
546
+ },
547
+ })),
548
+ });
549
+ }
550
+ else if (texts.length > 0) {
551
+ out.push({ role: "assistant", content: texts.join("\n") });
552
+ }
553
+ }
554
+ else if (message.role === "tool") {
555
+ const toolContent = recordArray(message.content);
556
+ assertProviderContentParts(toolContent, "tool results");
557
+ for (const content of toolContent) {
558
+ const toolCallId = stringValue(content.toolCallId) ?? "";
559
+ if (!pairedIds.has(toolCallId))
560
+ continue;
561
+ const output = unwrapToolResult(content.result ?? content.output);
562
+ out.push({ role: "tool", tool_call_id: toolCallId, content: output });
563
+ const images = imageParts(content);
564
+ if (images.length > 0) {
565
+ if (!allowImages)
566
+ throw imageContentError("tool results");
567
+ out.push({
568
+ role: "user",
569
+ content: images
570
+ .map((p) => filePartToOpenAIImageUrl(p))
571
+ .filter(Boolean),
572
+ });
573
+ }
574
+ }
575
+ }
576
+ }
577
+ return out;
578
+ }
579
+ function promptToAnthropicMessages(prompt, allowImages) {
580
+ const out = [];
581
+ const pairedIds = completeToolCallIds(prompt);
582
+ for (const message of prompt) {
583
+ if (message.role === "system")
584
+ continue;
585
+ if (message.role === "user") {
586
+ out.push({ role: "user", content: anthropicUserContent(message.content, allowImages) });
587
+ }
588
+ else if (message.role === "assistant") {
589
+ const parts = recordArray(message.content);
590
+ const content = [];
591
+ for (const p of parts) {
592
+ if (p.type === "text") {
593
+ const text = textFromPart(p);
594
+ if (text)
595
+ content.push({ type: "text", text });
596
+ }
597
+ else if (p.type === "tool-call") {
598
+ const id = stringValue(p.toolCallId);
599
+ if (!id || !pairedIds.has(id))
600
+ continue;
601
+ content.push({
602
+ type: "tool_use",
603
+ id,
604
+ name: stringValue(p.toolName) ?? "",
605
+ input: recordOrEmpty(p.input ?? p.args ?? p.arguments),
606
+ });
607
+ }
608
+ }
609
+ if (content.length > 0)
610
+ out.push({ role: "assistant", content });
611
+ }
612
+ else if (message.role === "tool") {
613
+ const toolContent = recordArray(message.content);
614
+ assertProviderContentParts(toolContent, "tool results");
615
+ for (const content of toolContent) {
616
+ const toolCallId = stringValue(content.toolCallId) ?? "";
617
+ if (!pairedIds.has(toolCallId))
618
+ continue;
619
+ const output = unwrapToolResult(content.result ?? content.output);
620
+ out.push({
621
+ role: "user",
622
+ content: [{ type: "tool_result", tool_use_id: toolCallId, content: output }],
623
+ });
624
+ const images = imageParts(content);
625
+ if (images.length > 0) {
626
+ if (!allowImages)
627
+ throw imageContentError("tool results");
628
+ out.push({
629
+ role: "user",
630
+ content: images
631
+ .map((p) => filePartToAnthropicImage(p))
632
+ .filter(Boolean),
633
+ });
634
+ }
635
+ }
636
+ }
637
+ }
638
+ return out;
639
+ }
640
+ /**
641
+ * OpenAI Chat Completions body for POST /provider/v1/chat/completions.
642
+ * Docs: https://commandcode.ai/docs/provider#quickstart / #streaming — follows
643
+ * OpenAI schema; streaming example shows stream:true + stream_options:{include_usage:true}.
644
+ * Provider FAQ: text + images only; max_tokens honoured for both transports (capped 64k).
645
+ * reasoning_effort is CLI extension via reasoning.ts (not in provider docs), byte-equivalent to shared helpers.
646
+ */
647
+ function buildOpenAIBody(options) {
648
+ const prompt = options.prompt ?? [];
649
+ const allowImages = options.allowImages ?? false;
650
+ if (!allowImages)
651
+ assertTextOnlyMessages(prompt);
652
+ const messages = promptToOpenAIMessages(prompt, allowImages);
653
+ const body = {
654
+ model: options.model ?? "",
655
+ stream: true,
656
+ messages,
657
+ };
658
+ if (options.tools) {
659
+ const t = openAITools(options.tools);
660
+ if (t)
661
+ body.tools = t;
662
+ }
663
+ // Anthropic requires max_tokens; for OpenAI we set capped default to honour #52
664
+ // (byte-equivalent to shared cappedMaxTokens). Provider models list shows context_length up to 1M
665
+ // but transport caps at DEFAULT_PROVIDER_MAX_TOKENS.
666
+ const maxTokens = cappedMaxTokens(options.maxOutputTokens);
667
+ body.max_tokens = maxTokens;
668
+ // System is already inside messages for OpenAI, but also accept explicit systemPrompt
669
+ const explicitSystem = systemPromptToText(options.systemPrompt);
670
+ if (explicitSystem && !prompt.some((m) => m.role === "system")) {
671
+ body.messages = [{ role: "system", content: explicitSystem }, ...messages];
672
+ }
673
+ const effort = reasoningEffortFor(options.providerOptions, options.model);
674
+ if (effort)
675
+ body.reasoning_effort = effort;
676
+ // OpenAI streaming usage needs stream_options — doc streaming example includes it
677
+ body.stream_options = { include_usage: true };
678
+ return body;
679
+ }
680
+ /**
681
+ * Anthropic Messages body for POST /provider/v1/messages.
682
+ * Docs: https://commandcode.ai/docs/provider — follows Anthropic schema; system is
683
+ * top-level string (not in messages), stream:true, max_tokens required (we default/cap 64k).
684
+ * reasoning_effort same CLI extension as OpenAI path.
685
+ */
686
+ function buildAnthropicBody(options) {
687
+ const prompt = options.prompt ?? [];
688
+ const allowImages = options.allowImages ?? false;
689
+ if (!allowImages)
690
+ assertTextOnlyMessages(prompt);
691
+ const system = promptSystemText(prompt) || systemPromptToText(options.systemPrompt);
692
+ const messages = promptToAnthropicMessages(prompt, allowImages);
693
+ const body = {
694
+ model: options.model ?? "",
695
+ stream: true,
696
+ messages,
697
+ };
698
+ if (system)
699
+ body.system = system;
700
+ if (options.tools) {
701
+ const t = anthropicTools(options.tools);
702
+ if (t)
703
+ body.tools = t;
704
+ }
705
+ const maxTokens = cappedMaxTokens(options.maxOutputTokens);
706
+ body.max_tokens = maxTokens;
707
+ const effort = reasoningEffortFor(options.providerOptions, options.model);
708
+ if (effort)
709
+ body.reasoning_effort = effort;
710
+ return body;
711
+ }
712
+ // Primary codec helpers — naming follows spec sketch but aliases are provided
713
+ export function messagesToOpenAI(prompt, options = {}) {
714
+ const p = prompt ?? options.prompt ?? [];
715
+ // Support both call shapes: messagesToOpenAI(prompt, opts) and messagesToOpenAI({prompt, model, ...})
716
+ if (Array.isArray(prompt) && isRecord(options) && "prompt" in options) {
717
+ // Called as messagesToOpenAI({ prompt, model, ... })
718
+ return buildOpenAIBody(options);
719
+ }
720
+ if (Array.isArray(prompt)) {
721
+ return buildOpenAIBody({ prompt: p, ...options });
722
+ }
723
+ // Called as messagesToOpenAI(optionsObject)
724
+ return buildOpenAIBody(prompt);
725
+ }
726
+ export function messagesToAnthropic(prompt, options = {}) {
727
+ const p = prompt ?? options.prompt ?? [];
728
+ if (Array.isArray(prompt) && isRecord(options) && "prompt" in options) {
729
+ return buildAnthropicBody(options);
730
+ }
731
+ if (Array.isArray(prompt)) {
732
+ return buildAnthropicBody({ prompt: p, ...options });
733
+ }
734
+ return buildAnthropicBody(prompt);
735
+ }
736
+ // Canonical codec entry points: messagesToOpenAI / messagesToAnthropic.
737
+ export { openAITools, anthropicTools };
@@ -1,2 +1,13 @@
1
1
  export declare function redactCommandCodeErrorText(value: string): string;
2
+ /**
3
+ * Detects the documented Provider API `403 upgrade_required` signal — "You're
4
+ * on the Go plan, the only plan without API access. Upgrade to GOAT or
5
+ * higher." (https://commandcode.ai/docs/provider). Tolerant to the documented
6
+ * variants: the JSON `error.code` / `error.type` may be `upgrade_required` and
7
+ * the message may phrase the same upgrade intent ("without API access",
8
+ * "Upgrade to GOAT"). A 403 that merely mentions the Go plan without that
9
+ * intent (e.g. "forbidden") is not a flip signal, and any status other than
10
+ * 403 (401, 422 cmd_zdr_no_providers, 429, 5xx, ...) never is either.
11
+ */
12
+ export declare function isUpgradeRequiredError(status: number, body: unknown): boolean;
2
13
  export declare function commandCodeErrorMessage(value: unknown): string | undefined;
@@ -26,6 +26,44 @@ export function redactCommandCodeErrorText(value) {
26
26
  function isRecord(value) {
27
27
  return typeof value === "object" && value !== null;
28
28
  }
29
+ /**
30
+ * Detects the documented Provider API `403 upgrade_required` signal — "You're
31
+ * on the Go plan, the only plan without API access. Upgrade to GOAT or
32
+ * higher." (https://commandcode.ai/docs/provider). Tolerant to the documented
33
+ * variants: the JSON `error.code` / `error.type` may be `upgrade_required` and
34
+ * the message may phrase the same upgrade intent ("without API access",
35
+ * "Upgrade to GOAT"). A 403 that merely mentions the Go plan without that
36
+ * intent (e.g. "forbidden") is not a flip signal, and any status other than
37
+ * 403 (401, 422 cmd_zdr_no_providers, 429, 5xx, ...) never is either.
38
+ */
39
+ export function isUpgradeRequiredError(status, body) {
40
+ if (status !== 403)
41
+ return false;
42
+ const candidates = [];
43
+ const pushStrings = (record) => {
44
+ for (const key of ["code", "type", "message"]) {
45
+ const part = record[key];
46
+ if (typeof part === "string")
47
+ candidates.push(part);
48
+ }
49
+ };
50
+ if (typeof body === "string") {
51
+ candidates.push(body);
52
+ try {
53
+ body = JSON.parse(body);
54
+ }
55
+ catch {
56
+ // keep the raw text as the only candidate below
57
+ }
58
+ }
59
+ if (isRecord(body)) {
60
+ const error = body.error;
61
+ if (isRecord(error))
62
+ pushStrings(error);
63
+ pushStrings(body);
64
+ }
65
+ return candidates.some((c) => /upgrade_required|upgrade to goat|without api access/i.test(c));
66
+ }
29
67
  export function commandCodeErrorMessage(value) {
30
68
  if (typeof value === "string")
31
69
  return value;
@@ -3,3 +3,9 @@ export declare function parseStreamEventLine(line: string): unknown | undefined;
3
3
  export declare function mapFinishReason(reason: unknown): LanguageModelV3FinishReason;
4
4
  export declare function ccUsageToAiSdkUsage(event: Record<string, unknown>): LanguageModelV3Usage | undefined;
5
5
  export declare function ccEventToStreamPart(event: unknown): LanguageModelV3StreamPart[];
6
+ export declare function openAIUsageToAiSdkUsage(event: Record<string, unknown>): LanguageModelV3Usage | undefined;
7
+ export declare function anthropicUsageToAiSdkUsage(event: Record<string, unknown>): LanguageModelV3Usage | undefined;
8
+ export declare function openAIEventToStreamPart(event: unknown): LanguageModelV3StreamPart[];
9
+ export declare function anthropicEventToStreamPart(event: unknown): LanguageModelV3StreamPart[];
10
+ export declare function createOpenAIStreamParser(): (event: unknown) => LanguageModelV3StreamPart[];
11
+ export declare function createAnthropicStreamParser(): (event: unknown) => LanguageModelV3StreamPart[];