@visual-z/xeno 0.1.4 → 0.2.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 (146) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +143 -33
  3. package/docs/install.md +18 -6
  4. package/docs/providers.md +147 -0
  5. package/package.json +8 -2
  6. package/src/apps/cli.ts +42 -14
  7. package/src/apps/commands.ts +32 -4
  8. package/src/apps/completions.ts +9 -6
  9. package/src/apps/export-cli.ts +24 -0
  10. package/src/apps/extension-ui.ts +330 -0
  11. package/src/apps/input.ts +16 -1
  12. package/src/apps/model-catalog.ts +42 -0
  13. package/src/apps/project-trust.ts +49 -0
  14. package/src/apps/render.ts +1 -0
  15. package/src/apps/resource-browser.ts +75 -0
  16. package/src/apps/resources.ts +25 -0
  17. package/src/apps/session-browser.ts +113 -0
  18. package/src/apps/session-picker.ts +55 -0
  19. package/src/apps/shell.ts +1177 -378
  20. package/src/apps/startup.ts +400 -268
  21. package/src/core/action-defaults.ts +306 -0
  22. package/src/core/attachment-input.ts +17 -0
  23. package/src/core/catalog-merge.ts +19 -0
  24. package/src/core/event-validation.ts +19 -2
  25. package/src/core/events.ts +45 -2
  26. package/src/core/export.ts +6 -2
  27. package/src/core/extension-flags.ts +14 -0
  28. package/src/core/images.ts +46 -0
  29. package/src/core/json-event.ts +2 -0
  30. package/src/core/keymap.ts +43 -119
  31. package/src/core/model-replay.ts +22 -0
  32. package/src/core/model-scope.ts +53 -0
  33. package/src/core/packages.ts +47 -0
  34. package/src/core/project.ts +21 -3
  35. package/src/core/prompts.ts +33 -5
  36. package/src/core/queue.ts +18 -0
  37. package/src/core/resource-selection.ts +36 -0
  38. package/src/{runtime → core}/resume.ts +15 -22
  39. package/src/core/session-archive.ts +4 -0
  40. package/src/core/session-browser.ts +82 -0
  41. package/src/core/session-entry.ts +56 -0
  42. package/src/core/session-position.ts +26 -0
  43. package/src/core/settings.ts +169 -154
  44. package/src/core/theme.ts +15 -6
  45. package/src/core/thinking.ts +26 -0
  46. package/src/core/tokens.ts +2 -0
  47. package/src/core/transcript.ts +15 -5
  48. package/src/runtime/auth.ts +116 -11
  49. package/src/runtime/catalog.ts +54 -151
  50. package/src/runtime/clipboard.ts +114 -0
  51. package/src/runtime/extension-loader.ts +37 -0
  52. package/src/runtime/extension-ui.ts +55 -0
  53. package/src/runtime/extensions.ts +273 -0
  54. package/src/runtime/images.ts +76 -0
  55. package/src/runtime/loop.ts +213 -83
  56. package/src/runtime/model-config.ts +232 -0
  57. package/src/runtime/model-data.LICENSE +27 -0
  58. package/src/runtime/model-data.ts +21 -0
  59. package/src/runtime/model-replay.ts +10 -0
  60. package/src/runtime/model-scope.ts +20 -0
  61. package/src/runtime/model-sources.ts +151 -0
  62. package/src/runtime/model.ts +74 -11
  63. package/src/runtime/models-fetch.ts +6 -104
  64. package/src/runtime/oauth.ts +4 -3
  65. package/src/runtime/openai-responses.ts +12 -4
  66. package/src/runtime/openai.ts +13 -4
  67. package/src/runtime/package-manager.ts +125 -0
  68. package/src/runtime/pi-provider.ts +235 -0
  69. package/src/runtime/progress.ts +26 -0
  70. package/src/runtime/provider-access.ts +21 -0
  71. package/src/runtime/provider-protocols.ts +29 -0
  72. package/src/runtime/provider-request.ts +73 -0
  73. package/src/runtime/provider.ts +26 -6
  74. package/src/runtime/rich-text.ts +36 -0
  75. package/src/runtime/tools.ts +41 -5
  76. package/src/store/command.ts +7 -1
  77. package/src/store/context-files.ts +1 -1
  78. package/src/store/log.ts +65 -0
  79. package/src/store/models.ts +28 -9
  80. package/src/store/observations.ts +4 -2
  81. package/src/store/packages.ts +98 -0
  82. package/src/store/recovery.ts +8 -0
  83. package/src/store/resource-index.ts +170 -0
  84. package/src/store/resources.ts +75 -142
  85. package/src/store/session-archive.ts +162 -0
  86. package/src/store/session-browser.ts +68 -0
  87. package/src/store/session-position.ts +27 -0
  88. package/src/store/sessions.ts +87 -66
  89. package/src/store/trust.ts +13 -0
  90. package/src/tui/chat-viewport.ts +47 -0
  91. package/src/tui/editor.ts +62 -15
  92. package/src/tui/host.ts +50 -9
  93. package/src/tui/input.ts +8 -2
  94. package/src/tui/select.ts +24 -6
  95. package/src/tui/settings.ts +4 -0
  96. package/src/tui/system-theme.ts +700 -0
  97. package/src/tui/transcript.ts +63 -11
  98. package/src/tui/tree.ts +161 -95
  99. package/src/vendor/pi-rich/LICENSE.upstream +21 -0
  100. package/src/vendor/pi-rich/UPSTREAM.json +17 -0
  101. package/src/vendor/pi-rich/clipboard-command.ts +44 -0
  102. package/src/vendor/pi-rich/exif-orientation.ts +182 -0
  103. package/src/vendor/pi-rich/html.ts +51 -0
  104. package/src/vendor/pi-rich/image-convert.ts +49 -0
  105. package/src/vendor/pi-rich/image-resize-core.ts +164 -0
  106. package/src/vendor/pi-rich/image-resize-worker.ts +42 -0
  107. package/src/vendor/pi-rich/image-resize.ts +123 -0
  108. package/src/vendor/pi-rich/mime.ts +116 -0
  109. package/src/vendor/pi-rich/photon.ts +139 -0
  110. package/src/vendor/pi-rich/syntax-highlight.ts +214 -0
  111. package/src/vendor/pi-tui/UPSTREAM.json +50 -0
  112. package/src/vendor/pi-tui/VENDOR.md +7 -5
  113. package/src/vendor/pi-tui/autocomplete.ts +35 -10
  114. package/src/vendor/pi-tui/colors.ts +367 -0
  115. package/src/vendor/pi-tui/components/box.ts +8 -9
  116. package/src/vendor/pi-tui/components/editor.ts +4 -2
  117. package/src/vendor/pi-tui/components/markdown.ts +8 -2
  118. package/src/vendor/pi-tui/index.ts +31 -1
  119. package/src/vendor/pi-tui/native/UPSTREAM.json +23 -0
  120. package/src/vendor/pi-tui/native/clipboard.h +208 -0
  121. package/src/vendor/pi-tui/native/darwin/README.md +22 -0
  122. package/src/vendor/pi-tui/native/darwin/build.sh +69 -0
  123. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  124. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  125. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +132 -0
  126. package/src/vendor/pi-tui/native/linux/README.md +23 -0
  127. package/src/vendor/pi-tui/native/linux/build.sh +65 -0
  128. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  129. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  130. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +70 -0
  131. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +274 -0
  132. package/src/vendor/pi-tui/native/napi.h +91 -0
  133. package/src/vendor/pi-tui/native/win32/README.md +31 -0
  134. package/src/vendor/pi-tui/native/win32/build.mjs +232 -0
  135. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  136. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  137. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +241 -0
  138. package/src/vendor/pi-tui/native-platform.ts +2 -0
  139. package/src/vendor/pi-tui/oklab.ts +233 -0
  140. package/src/vendor/pi-tui/terminal-colors.ts +26 -8
  141. package/src/vendor/pi-tui/terminal-image.ts +43 -8
  142. package/src/vendor/pi-tui/terminal.ts +16 -9
  143. package/src/vendor/pi-tui/tui-alt-screen.ts +26 -18
  144. package/src/vendor/pi-tui/tui.ts +112 -75
  145. package/src/vendor/pi-tui/utils.ts +89 -49
  146. package/src/vendor/pi-tui/wheel-scroll.ts +82 -0
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import type { AssistantBlock } from "../core/events.ts";
21
21
  import type { ContentBlock, FrozenPrefix, Message } from "../core/project.ts";
22
+ import { imageData } from "./images.ts";
22
23
  import { assertNever } from "../core/exhaustive.ts";
23
24
  import type { OnDelta, Provider, ProviderConfig, ToolSchema, Turn } from "./provider.ts";
24
25
  import { pushSse, HttpError, StreamError } from "./openai.ts";
@@ -69,6 +70,7 @@ function inputItems(messages: readonly Message[]): InputItem[] {
69
70
  break;
70
71
  }
71
72
  case "tool_result":
73
+ case "image":
72
74
  break;
73
75
  default:
74
76
  return assertNever(block, "responses assistant input");
@@ -77,14 +79,16 @@ function inputItems(messages: readonly Message[]): InputItem[] {
77
79
  flushText();
78
80
  } else {
79
81
  const text: string[] = [];
82
+ const images: unknown[] = [];
80
83
  for (const block of message.content as readonly ContentBlock[]) {
81
84
  if (block.type === "text") text.push(block.text);
85
+ else if (block.type === "image") images.push({ type: "input_image", image_url: `data:${block.image.mimeType};base64,${imageData(block)}`, detail: "auto" });
82
86
  else if (block.type === "tool_result") {
83
87
  out.push({ type: "function_call_output", call_id: block.tool_use_id,
84
88
  output: block.is_error ? `[error] ${block.content}` : block.content });
85
89
  }
86
90
  }
87
- if (text.length > 0) out.push({ type: "message", role: "user", content: [{ type: "input_text", text: text.join("\n") }] });
91
+ if (text.length > 0 || images.length) out.push({ type: "message", role: "user", content: [...(text.length ? [{ type: "input_text", text: text.join("\n") }] : []), ...images] });
88
92
  }
89
93
  }
90
94
  return out;
@@ -108,9 +112,11 @@ export function buildResponsesRequest(config: ProviderConfig, frozen: FrozenPref
108
112
  if (frozen.tools.length > 0) request["tools"] = responsesTools(frozen.tools);
109
113
  if (spec.reasoning) {
110
114
  // Accepted but no longer required by current models; older ones need it.
111
- request["include"] = ["reasoning.encrypted_content"];
115
+ if (config.effort !== "off" || spec.provider === "xai") request["include"] = ["reasoning.encrypted_content"];
112
116
  const mapped = spec.thinkingLevelMap?.[config.effort];
113
- if (mapped !== null) request["reasoning"] = { effort: mapped ?? config.effort, summary: "auto" };
117
+ if (mapped !== null && !(config.effort === "off" && spec.provider === "github-copilot")) {
118
+ request["reasoning"] = { effort: mapped ?? (config.effort === "off" ? "none" : config.effort), ...(config.effort === "off" ? {} : { summary: "auto" }) };
119
+ }
114
120
  }
115
121
  return request;
116
122
  }
@@ -152,6 +158,7 @@ async function readResponsesSse(body: ReadableStream<Uint8Array>, node: number,
152
158
  case "response.output_item.added": {
153
159
  const kind = item?.["type"] === "reasoning" ? "thinking" : item?.["type"] === "function_call" ? "call" : item?.["type"] === "message" ? "text" : null;
154
160
  if (kind !== null) slots.set(index, { kind, text: "", ...(item ? { item } : {}) });
161
+ if (kind === "call") onDelta?.({ kind: "tool", id: String(item?.["call_id"] ?? index), name: String(item?.["name"] ?? ""), arguments: "" });
155
162
  return;
156
163
  }
157
164
  case "response.reasoning_summary_text.delta":
@@ -165,6 +172,7 @@ async function readResponsesSse(body: ReadableStream<Uint8Array>, node: number,
165
172
  slot.text += delta;
166
173
  if (slot.kind === "thinking") onDelta?.({ kind: "thinking", text: delta });
167
174
  else if (slot.kind === "text") onDelta?.({ kind: "text", text: delta });
175
+ else onDelta?.({ kind: "tool", id: String(slot.item?.["call_id"] ?? index), name: String(slot.item?.["name"] ?? ""), arguments: slot.text });
168
176
  return;
169
177
  }
170
178
  case "response.output_item.done": {
@@ -260,7 +268,7 @@ export class OpenAIResponsesProvider implements Provider {
260
268
 
261
269
  send(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta): Promise<Turn> {
262
270
  return withRetry(() => this.attempt(frozen, messages, node, signal, onDelta), this.retry, undefined,
263
- (attempt, delayMs, error) => { onDelta?.({ kind: "discard" }); this.onRetry?.(attempt, delayMs, error); }, signal);
271
+ (attempt, delayMs, error) => { onDelta?.({ kind: "discard" }); onDelta?.({ kind: "retry", attempt, delayMs }); this.onRetry?.(attempt, delayMs, error); }, signal);
264
272
  }
265
273
 
266
274
  private async attempt(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta): Promise<Turn> {
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import type { Message, ContentBlock, FrozenPrefix } from "../core/project.ts";
23
+ import { imageData } from "./images.ts";
23
24
  import type { AssistantBlock } from "../core/events.ts";
24
25
  import { assertNever } from "../core/exhaustive.ts";
25
26
  import type { Provider, ProviderConfig, Turn, ToolSchema, OnDelta } from "./provider.ts";
@@ -29,7 +30,7 @@ import { withRetry, DEFAULT_RETRY, type RetryPolicy } from "./retry.ts";
29
30
  /** One message as Chat Completions wants it. */
30
31
  export interface WireMessage {
31
32
  role: "system" | "developer" | "user" | "assistant" | "tool";
32
- content?: string | null;
33
+ content?: string | null | ({ type: "text"; text: string } | { type: "image_url"; image_url: { url: string } })[];
33
34
  reasoning_content?: string;
34
35
  /** OpenRouter's structured reasoning, replayed as received. */
35
36
  reasoning_details?: unknown;
@@ -150,6 +151,7 @@ function assistantMessage(content: readonly ContentBlock[], compat: Compat, reas
150
151
  });
151
152
  break;
152
153
  case "tool_result":
154
+ case "image":
153
155
  // A tool_result inside an assistant turn is not a shape the projection
154
156
  // produces. Ignored rather than thrown: the request stays valid, and
155
157
  // the pairing checks that would catch it live in the core.
@@ -190,9 +192,11 @@ function assistantMessage(content: readonly ContentBlock[], compat: Compat, reas
190
192
  function userMessages(content: readonly ContentBlock[]): WireMessage[] {
191
193
  const out: WireMessage[] = [];
192
194
  const text: string[] = [];
195
+ const parts: NonNullable<Exclude<WireMessage["content"], string>> = [];
193
196
 
194
197
  for (const b of content) {
195
198
  switch (b.type) {
199
+ case "image": parts.push({ type: "image_url", image_url: { url: `data:${b.image.mimeType};base64,${imageData(b)}` } }); break;
196
200
  case "tool_result":
197
201
  out.push({
198
202
  role: "tool",
@@ -213,7 +217,8 @@ function userMessages(content: readonly ContentBlock[]): WireMessage[] {
213
217
  }
214
218
  }
215
219
 
216
- if (text.length > 0) out.push({ role: "user", content: text.join("\n") });
220
+ if (parts.length) out.push({ role: "user", content: [...(text.length ? [{ type: "text" as const, text: text.join("\n") }] : []), ...parts] });
221
+ else if (text.length > 0) out.push({ role: "user", content: text.join("\n") });
217
222
  return out;
218
223
  }
219
224
 
@@ -262,10 +267,12 @@ export function buildRequest(
262
267
  if (frozen.tools.length > 0) request.tools = toWireTools(frozen.tools);
263
268
 
264
269
  if (spec.reasoning) {
265
- if (compat.thinkingFormat === "deepseek") request.thinking = { type: "enabled" };
266
- if (compat.supportsReasoningEffort) {
270
+ if (compat.thinkingFormat === "deepseek") request.thinking = { type: config.effort === "off" ? "disabled" : "enabled" };
271
+ if (compat.supportsReasoningEffort && config.effort !== "off") {
267
272
  const effort = wireEffort(spec, config.effort);
268
273
  if (effort !== null) request.reasoning_effort = effort;
274
+ } else if (config.effort === "off" && compat.thinkingFormat !== "deepseek" && typeof spec.thinkingLevelMap?.off === "string") {
275
+ request.reasoning_effort = spec.thinkingLevelMap.off;
269
276
  }
270
277
  }
271
278
 
@@ -436,6 +443,7 @@ export class OpenAICompletionsProvider implements Provider {
436
443
  undefined,
437
444
  (attempt, delayMs, error) => {
438
445
  onDelta?.({ kind: "discard" });
446
+ onDelta?.({ kind: "retry", attempt, delayMs });
439
447
  this.onRetry?.(attempt, delayMs, error);
440
448
  },
441
449
  signal,
@@ -584,6 +592,7 @@ function applyCompletionChunk(chunk: WireStreamChunk, acc: StreamAcc, onDelta?:
584
592
  if (call.id) tool.id = call.id;
585
593
  if (call.function?.name) tool.name = call.function.name;
586
594
  if (call.function?.arguments) tool.arguments += call.function.arguments;
595
+ onDelta?.({ kind: "tool", id: tool.id || String(index), name: tool.name, arguments: tool.arguments });
587
596
  Object.assign(tool.extra, extraFields(call as Record<string, unknown>) ?? {});
588
597
  }
589
598
  }
@@ -0,0 +1,125 @@
1
+ /** Explicit package operations, isolated from agent sessions and provider credentials. */
2
+ import { mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
3
+ import { join, relative, resolve } from "node:path";
4
+ import { packageConfig, type PackageDeclaration } from "../core/packages.ts";
5
+ import { configDir, readJsonFile } from "../store/config.ts";
6
+ import { serialized } from "../store/serial.ts";
7
+ import { projectTrust } from "../store/trust.ts";
8
+ import { packageSource, packageHome, packageKey, packageRecords, installedPackage, configuredPackages, savePackageDeclaration, savePackageRecord, removePackageRecords, packageManifest, type PackageRecord } from "../store/packages.ts";
9
+
10
+ export type PackageOperation = "install" | "remove" | "update" | "list";
11
+ export interface PackageCommand { operation: PackageOperation; sources: string[]; local?: boolean }
12
+ export type PackageExecutor = (argv: string[], options: { cwd: string; signal?: AbortSignal }) => Promise<{ code: number; stdout: string; stderr: string }>;
13
+ export interface PackageOptions { workspace: string; userDir?: string; run?: PackageExecutor; signal?: AbortSignal; say?: (text: string) => void; trusted?: boolean }
14
+ export interface ListedPackage { source: string; identity: string; scope: "user" | "project"; path?: string; pinned: boolean; filtered: boolean }
15
+
16
+ export const executePackageCommand: PackageExecutor = async (argv, options) => {
17
+ if (options.signal?.aborted) throw Error("Package operation cancelled");
18
+ const child=Bun.spawn(argv,{cwd:options.cwd,stdin:"ignore",stdout:"pipe",stderr:"pipe"});
19
+ const abort=()=>{child.kill();}; options.signal?.addEventListener("abort",abort,{once:true});
20
+ try {
21
+ const [stdout,stderr,code]=await Promise.all([new Response(child.stdout).text(),new Response(child.stderr).text(),child.exited]);
22
+ if(options.signal?.aborted)throw Error("Package operation cancelled"); return {stdout,stderr,code};
23
+ } finally { options.signal?.removeEventListener("abort",abort); }
24
+ };
25
+
26
+ export class PackageManager {
27
+ readonly options: PackageOptions;
28
+ constructor(options: PackageOptions) { this.options={...options,workspace:resolve(options.workspace)}; }
29
+ private base(local=false):string { return local?join(this.options.workspace,".xeno"):this.options.userDir??configDir(); }
30
+ private async command(argv:string[],cwd:string):Promise<string> {
31
+ const result=await (this.options.run??executePackageCommand)(argv,{cwd,...(this.options.signal?{signal:this.options.signal}:{})});
32
+ if(result.code!==0)throw Error(`${argv[0]} exited ${result.code}: ${result.stderr.trim()||result.stdout.trim()}`);
33
+ return result.stdout.trim();
34
+ }
35
+ async list():Promise<ListedPackage[]> {
36
+ const entries:ListedPackage[]=[];
37
+ for(const scope of ["user","project"] as const){const base=this.base(scope==="project");for(const declaration of await configuredPackages(base)){
38
+ const config=packageConfig(declaration),source=packageSource(config.source,base),path=await installedPackage(source,base);
39
+ entries.push({source:config.source,identity:source.id,scope,...(path?{path}:{}),pinned:source.pinned,filtered:typeof declaration!=="string"});
40
+ }}
41
+ return entries;
42
+ }
43
+ async install(raw:string,options:{local?:boolean;persist?:boolean;base?:string;declaration?:PackageDeclaration}={}):Promise<string> {
44
+ if(this.options.signal?.aborted)throw Error("Package operation cancelled");
45
+ const base=options.base??this.base(options.local),source=packageSource(raw,this.options.workspace);
46
+ return serialized(join(packageHome(base),"operation"),async()=>{
47
+ // Parse existing metadata before any network/installation side effect.
48
+ await configuredPackages(base); const records=await packageRecords(base),previous=records[packageKey(source)];
49
+ const declaration=options.declaration??(source.kind==="local"?relative(base,source.path)||".":source.source);
50
+ if(source.kind==="local") {
51
+ if(!await stat(source.path).then(()=>true,()=>false))throw Error(`Package path does not exist: ${source.path}`);
52
+ await packageManifest(source.path);
53
+ if(options.persist!==false)await savePackageDeclaration(base,source,declaration);
54
+ return source.path;
55
+ }
56
+ const versions=join(packageHome(base),"versions"); await mkdir(versions,{recursive:true});
57
+ const staging=await mkdtemp(join(versions,packageKey(source)+"-")); let published=false;
58
+ try {
59
+ let root:string,revision:string|undefined;
60
+ if(source.kind==="npm") {
61
+ await Bun.write(join(staging,"package.json"),JSON.stringify({name:"xeno-package-install",private:true}));
62
+ await this.command([process.execPath,"add","--exact","--production","--omit","peer",source.spec],staging);
63
+ root=join(staging,"node_modules",source.name);
64
+ } else {
65
+ root=join(staging,"repo"); await this.command(["git","clone","--",source.repo,root],staging);
66
+ if(source.ref)await this.command(["git","checkout","--detach",source.ref],root);
67
+ revision=await this.command(["git","rev-parse","HEAD"],root);
68
+ if(await Bun.file(join(root,"package.json")).exists()) await this.command([process.execPath,"install","--production","--omit","peer"],root);
69
+ }
70
+ if(!await stat(root).then(s=>s.isDirectory(),()=>false))throw Error("Package installation produced no package directory");
71
+ await packageManifest(root);
72
+ const manifest=await readJsonFile<{version?:string}>(join(root,"package.json"));if(manifest.error)throw Error(manifest.error);
73
+ const record:PackageRecord={source:source.source,identity:source.id,path:relative(packageHome(base),root),...(manifest.value?.version?{version:manifest.value.version}:{}),...(revision?{revision}:{})};
74
+ await savePackageRecord(base,source,record);
75
+ try { if(options.persist!==false)await savePackageDeclaration(base,source,declaration); }
76
+ catch(error){await savePackageRecord(base,source,previous);throw error;}
77
+ // Old versions remain available to already-running extension generations.
78
+ published=true; return root;
79
+ } finally { if(!published)await rm(staging,{recursive:true,force:true}); }
80
+ });
81
+ }
82
+ async remove(raw:string,local=false):Promise<boolean> {
83
+ const base=this.base(local),requested=packageSource(raw,this.options.workspace);
84
+ return serialized(join(packageHome(base),"operation"),async()=>{
85
+ const declarations=await configuredPackages(base),match=declarations.find(entry=>{const source=packageSource(packageConfig(entry).source,base);return source.id===requested.id||source.source===raw||source.kind==="npm"&&source.name===raw;});
86
+ if(!match)return false;
87
+ const source=packageSource(packageConfig(match).source,base);
88
+ await packageRecords(base);
89
+ await savePackageDeclaration(base,source,undefined);const keys=await removePackageRecords(base,source.id);
90
+ if(source.kind!=="local") {
91
+ const versions=join(packageHome(base),"versions"),names=await readdir(versions).catch(()=>[]);
92
+ for(const name of names)if(keys.some(key=>name.startsWith(key+"-")))await rm(join(versions,name),{recursive:true,force:true});
93
+ }
94
+ return true;
95
+ });
96
+ }
97
+ async update(raw?:string,local?:boolean):Promise<number> {
98
+ let count=0;
99
+ const trusted=this.options.trusted??(await projectTrust(this.options.workspace)).trusted;
100
+ for(const scope of [false,true]) {
101
+ if(local!==undefined&&scope!==local)continue;
102
+ if(scope&&local!==true&&!trusted)continue;
103
+ const base=this.base(scope);
104
+ for(const declaration of await configuredPackages(base)) {
105
+ const source=packageSource(packageConfig(declaration).source,base);
106
+ if(raw&&source.source!==raw&&source.id!==packageSource(raw,this.options.workspace).id&&!(source.kind==="npm"&&source.name===raw))continue;
107
+ if(source.kind==="local")continue;
108
+ await this.install(source.source,{base,persist:false,declaration});count++;
109
+ }
110
+ }
111
+ if(raw&&count===0)throw Error(`No installed remote package matches ${raw}`);return count;
112
+ }
113
+ async temporary(raw:string):Promise<string> { return this.install(raw,{base:join(this.base(),"temporary"),persist:false}); }
114
+ }
115
+
116
+ export async function runPackageCommand(command:PackageCommand,options:PackageOptions):Promise<void> {
117
+ const manager=new PackageManager(options),say=options.say??console.log;
118
+ if(!await stat(manager.options.workspace).then(s=>s.isDirectory(),()=>false))throw Error(`Workspace does not exist: ${options.workspace}`);
119
+ if(command.operation==="list"&&command.sources.length||command.operation==="update"&&command.sources.length>1)throw Error(`Unexpected sources for ${command.operation}`);
120
+ if(command.operation==="list") { for(const entry of await manager.list())say(`${entry.scope} ${entry.source}${entry.pinned?" (pinned)":""} · ${entry.path??"not installed"}`); return; }
121
+ if(command.operation==="update") { say(`Updated ${await manager.update(command.sources[0],command.local)} package(s)`); return; }
122
+ if(command.sources.length===0)throw Error(`${command.operation} requires a package source`);
123
+ for(const source of command.sources) if(command.operation==="install") say(`Installed ${source} · ${await manager.install(source,{...(command.local===undefined?{}:{local:command.local})})}`);
124
+ else say(await manager.remove(source,command.local)?`Removed ${source}`:`Package not configured: ${source}`);
125
+ }
@@ -0,0 +1,235 @@
1
+ /** Pi owns wire protocols; xeno owns retries, durable events, projection and credential precedence. */
2
+ import type { AssistantMessage, Context, Message as PiMessage, Model, Tool, ProviderStreams, SimpleStreamOptions } from "@earendil-works/pi-ai";
3
+ import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
4
+ import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
5
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
6
+ import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
7
+ import { azureOpenAIResponsesApi } from "@earendil-works/pi-ai/api/azure-openai-responses.lazy";
8
+ import { openAICodexResponsesApi } from "@earendil-works/pi-ai/api/openai-codex-responses.lazy";
9
+ import { googleGenerativeAIApi } from "@earendil-works/pi-ai/api/google-generative-ai.lazy";
10
+ import { googleVertexApi } from "@earendil-works/pi-ai/api/google-vertex.lazy";
11
+ import { bedrockConverseStreamApi } from "@earendil-works/pi-ai/api/bedrock-converse-stream.lazy";
12
+ import { mistralConversationsApi } from "@earendil-works/pi-ai/api/mistral-conversations.lazy";
13
+ import { piMessagesApi } from "@earendil-works/pi-ai/api/pi-messages.lazy";
14
+ import { assertNever } from "../core/exhaustive.ts";
15
+ import type { AssistantBlock } from "../core/events.ts";
16
+ import type { FrozenPrefix, Message } from "../core/project.ts";
17
+ import type { Api, ModelSpec } from "./model.ts";
18
+ import { providerRequest } from "./provider-request.ts";
19
+ import type { ResolveEffects } from "./auth.ts";
20
+ import { imageData } from "./images.ts";
21
+ import type { OnDelta, Provider, ProviderConfig, ToolSchema, Turn } from "./provider.ts";
22
+ import { DEFAULT_RETRY, withRetry, type RetryPolicy } from "./retry.ts";
23
+ import { HttpError, StreamError } from "./openai.ts";
24
+
25
+ const APIS: Record<Api, ProviderStreams> = {
26
+ "anthropic-messages": anthropicMessagesApi(), "openai-completions": openAICompletionsApi(),
27
+ "openai-responses": openAIResponsesApi(), "azure-openai-responses": azureOpenAIResponsesApi(),
28
+ "openai-codex-responses": openAICodexResponsesApi(), "google-generative-ai": googleGenerativeAIApi(),
29
+ "google-vertex": googleVertexApi(), "bedrock-converse-stream": bedrockConverseStreamApi(),
30
+ "mistral-conversations": mistralConversationsApi(), "pi-messages": piMessagesApi(),
31
+ };
32
+ function record(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
33
+ const EMPTY_USAGE: AssistantMessage["usage"] = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
34
+
35
+ /** Native envelopes keep their SDK-owned tools/system/signal when adding nested generation options. */
36
+ function mergePayload(payload: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
37
+ const next = { ...payload };
38
+ for (const [key, value] of Object.entries(patch)) {
39
+ const current = next[key];
40
+ next[key] = record(current) && record(value) ? mergePayload(current, value) : value;
41
+ }
42
+ return next;
43
+ }
44
+
45
+ /** Google exposes structured HTTP failures as JSON in its public stream error, without a fetch hook. */
46
+ function nativeStatus(message: string): number | undefined {
47
+ try {
48
+ const body: unknown = JSON.parse(message);
49
+ const code = record(body) && record(body["error"]) ? body["error"]["code"] : undefined;
50
+ return typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599 ? code : undefined;
51
+ } catch { return undefined; }
52
+ }
53
+
54
+ function piModel(spec: ModelSpec, baseUrl: string): Model<Api> {
55
+ return { id: spec.wireId ?? spec.id, provider: spec.provider, name: spec.name, api: spec.api, baseUrl,
56
+ reasoning: spec.reasoning, input: [...(spec.input ?? ["text"])],
57
+ // Required SDK metadata defaults are operational only; xeno still reports absent prices as unknown.
58
+ contextWindow: spec.contextWindow ?? 131_072, maxTokens: spec.maxTokens ?? 16_000,
59
+ cost: { input: spec.price?.input ?? 0, output: spec.price?.output ?? 0, cacheRead: spec.price?.cacheRead ?? 0, cacheWrite: spec.price?.cacheWrite ?? 0 },
60
+ ...(spec.thinkingLevelMap ? { thinkingLevelMap: spec.thinkingLevelMap } : {}),
61
+ ...(spec.nativeCompat || spec.compat ? { compat: { ...spec.compat, ...spec.nativeCompat } } : {}),
62
+ ...(spec.samplingParams ? { samplingParams: spec.samplingParams } : {}) } as Model<Api>;
63
+ }
64
+
65
+ type PiBlock = AssistantMessage["content"][number];
66
+ function thinkingOf(raw: unknown): Extract<PiBlock, { type: "thinking" }> | null {
67
+ if (!record(raw)) return null;
68
+ if (record(raw["pi"]) && raw["pi"]["type"] === "thinking" && typeof raw["pi"]["thinking"] === "string") {
69
+ return { ...structuredClone(raw["pi"]), type: "thinking", thinking: raw["pi"]["thinking"] };
70
+ }
71
+ if (raw["type"] === "thinking") return { type: "thinking", thinking: typeof raw["thinking"] === "string" ? raw["thinking"] : "", ...(typeof raw["signature"] === "string" ? { thinkingSignature: raw["signature"] } : {}) };
72
+ if (raw["type"] === "redacted_thinking" && typeof raw["data"] === "string") return { type: "thinking", thinking: "", redacted: true, thinkingSignature: raw["data"] };
73
+ if (record(raw["responses_item"])) return { type: "thinking", thinking: "", thinkingSignature: JSON.stringify(raw["responses_item"]) };
74
+ if (typeof raw["reasoning_content"] === "string") return { type: "thinking", thinking: raw["reasoning_content"], ...(raw["reasoning_details"] !== undefined ? { thinkingSignature: JSON.stringify(raw["reasoning_details"]) } : {}) };
75
+ return null;
76
+ }
77
+
78
+ /** Pure conversion of an already hydrated projection; no second transcript is retained. */
79
+ export function piContext(frozen: FrozenPrefix, messages: readonly Message[], spec: ModelSpec): Context {
80
+ const out: PiMessage[] = [];
81
+ const names = new Map<string, string>();
82
+ const ids = new Map<string, string>();
83
+ for (const message of messages) {
84
+ if (message.role === "assistant") {
85
+ const content: PiBlock[] = [];
86
+ for (const block of message.content) {
87
+ switch (block.type) {
88
+ case "text": {
89
+ const wire = record(block.wire) ? block.wire : {};
90
+ content.push({ type: "text", text: block.text, ...(typeof wire["textSignature"] === "string" ? { textSignature: wire["textSignature"] } : {}) }); break;
91
+ }
92
+ case "thinking": { const thinking = thinkingOf(block.raw); if (thinking) content.push(thinking); break; }
93
+ case "tool_use": {
94
+ names.set(block.id, block.name);
95
+ const wire = record(block.wire) ? block.wire : {};
96
+ const native = record(wire["pi"]) ? wire["pi"] : {};
97
+ const extra = record(wire["extra_content"]) && record(wire["extra_content"]["google"]) ? wire["extra_content"]["google"] : {};
98
+ const signature = native["thoughtSignature"] ?? extra["thought_signature"];
99
+ const id = typeof wire["item_id"] === "string" && spec.api.includes("responses") ? `${block.id}|${wire["item_id"]}` : block.id;
100
+ ids.set(block.id, id);
101
+ content.push({ type: "toolCall", id, name: block.name, arguments: structuredClone(block.input) as Extract<PiBlock, { type: "toolCall" }>["arguments"],
102
+ ...(typeof signature === "string" ? { thoughtSignature: signature } : {}), ...(typeof native["namespace"] === "string" ? { namespace: native["namespace"] } : {}) }); break;
103
+ }
104
+ case "tool_result": case "image": break;
105
+ default: assertNever(block, "pi assistant block");
106
+ }
107
+ }
108
+ out.push({ role: "assistant", content, api: spec.api, provider: spec.provider, model: spec.wireId ?? spec.id,
109
+ timestamp: 0, usage: structuredClone(EMPTY_USAGE), stopReason: content.some(block => block.type === "toolCall") ? "toolUse" : "stop" });
110
+ } else {
111
+ let content: Extract<PiMessage, { role: "user" }>["content"] = [];
112
+ const flush = () => { if (content.length) out.push({ role: "user", content, timestamp: 0 }); content = []; };
113
+ for (const block of message.content) {
114
+ if (block.type === "tool_result") {
115
+ flush(); out.push({ role: "toolResult", toolCallId: ids.get(block.tool_use_id) ?? block.tool_use_id, toolName: names.get(block.tool_use_id) ?? "tool",
116
+ content: [{ type: "text", text: block.content }], isError: block.is_error ?? false, timestamp: 0 });
117
+ } else if (block.type === "text") content.push({ type: "text", text: block.text });
118
+ else if (block.type === "image") content.push({ type: "image", data: imageData(block), mimeType: block.image.mimeType });
119
+ }
120
+ flush();
121
+ }
122
+ }
123
+ return { systemPrompt: frozen.system, messages: out, tools: frozen.tools.map(value => {
124
+ const schema = value as ToolSchema;
125
+ return { name: schema.name, description: schema.description, parameters: schema.input_schema as Tool["parameters"] };
126
+ }) };
127
+ }
128
+
129
+ export function piBlocks(message: AssistantMessage): AssistantBlock[] {
130
+ return message.content.map((block): AssistantBlock => {
131
+ switch (block.type) {
132
+ case "text": return { type: "text", text: block.text, ...(block.textSignature ? { wire: { textSignature: block.textSignature } } : {}) };
133
+ case "thinking": {
134
+ let raw: unknown = { pi: structuredClone(block) };
135
+ if (message.api === "anthropic-messages") raw = block.redacted ? { type: "redacted_thinking", data: block.thinkingSignature }
136
+ : { type: "thinking", thinking: block.thinking, signature: block.thinkingSignature ?? "" };
137
+ else if (message.api.includes("responses") && block.thinkingSignature) { try { raw = { responses_item: JSON.parse(block.thinkingSignature) }; } catch { /* An opaque signature is still preserved by the envelope. */ } }
138
+ else if (message.api === "openai-completions") {
139
+ let details: unknown;
140
+ try { if (block.thinkingSignature) details = JSON.parse(block.thinkingSignature); } catch { /* The Pi envelope preserves non-JSON signatures. */ }
141
+ raw = { pi: structuredClone(block), reasoning_content: block.thinking, ...(details !== undefined ? { reasoning_details: details } : {}) };
142
+ }
143
+ return { type: "thinking", text: block.thinking, raw };
144
+ }
145
+ case "toolCall": return { type: "tool_use", call_id: block.id, name: block.name, input: structuredClone(block.arguments),
146
+ ...(block.thoughtSignature || block.namespace ? { wire: { pi: { thoughtSignature: block.thoughtSignature, namespace: block.namespace } } } : {}) };
147
+ default: return assertNever(block, "pi response block");
148
+ }
149
+ });
150
+ }
151
+
152
+ export class PiProvider implements Provider {
153
+ readonly config: ProviderConfig;
154
+ private readonly apiKey: string;
155
+ private readonly options: {
156
+ fetch?: typeof fetch; effects?: ResolveEffects; oauth?: boolean; retry?: RetryPolicy; onRetry?: (attempt: number, delayMs: number, error: unknown) => void;
157
+ };
158
+ constructor(config: ProviderConfig, apiKey: string, options: PiProvider["options"] = {}) {
159
+ this.config = config; this.apiKey = apiKey; this.options = options;
160
+ }
161
+
162
+ send(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta): Promise<Turn> {
163
+ // Bedrock's Pi adapter leaves retry ownership with the AWS SDK; never multiply that loop.
164
+ const retry = this.options.retry ?? (this.config.spec.api === "bedrock-converse-stream" ? { ...DEFAULT_RETRY, maxRetries: 0 } : DEFAULT_RETRY);
165
+ return withRetry(() => this.attempt(frozen, messages, node, signal, onDelta), retry, undefined,
166
+ (attempt, delayMs, error) => { onDelta?.({ kind: "discard" }); onDelta?.({ kind: "retry", attempt, delayMs }); this.options.onRetry?.(attempt, delayMs, error); }, signal);
167
+ }
168
+
169
+ private async attempt(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta): Promise<Turn> {
170
+ signal?.throwIfAborted();
171
+ const started = Date.now();
172
+ const request = providerRequest(this.config.spec, this.apiKey, this.options.effects, this.options.fetch, this.options.oauth);
173
+ const model = piModel(this.config.spec, request.baseUrl);
174
+ let status: number | undefined;
175
+ let responseHeaders = new Headers();
176
+ const observedFetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
177
+ const response = await request.fetch(input, init);
178
+ status = response.status; responseHeaders = new Headers(response.headers);
179
+ return response;
180
+ }) as typeof fetch;
181
+ const nativeHttp = ["google-generative-ai", "google-vertex", "bedrock-converse-stream"].includes(model.api);
182
+ const options: SimpleStreamOptions = { ...request.options, ...(request.key ? { apiKey: request.key } : {}),
183
+ env: request.env, headers: request.headers, ...(nativeHttp ? {} : { fetch: observedFetch }), maxRetries: 0, transport: "sse",
184
+ maxTokens: this.config.maxTokens, ...(this.config.effort !== "off" ? { reasoning: this.config.effort } : {}),
185
+ ...(signal ? { signal } : {}), onResponse: response => { status = response.status; responseHeaders = new Headers(response.headers); },
186
+ onPayload: payload => {
187
+ if (!record(payload)) return payload;
188
+ const next = mergePayload(payload, request.body);
189
+ // Stateless Responses is required by D8: never replace the recomputable projection with server state.
190
+ if (model.api.includes("responses")) { next["store"] = false; delete next["previous_response_id"]; }
191
+ return next;
192
+ } };
193
+ let completed: AssistantMessage | undefined;
194
+ // Pi emits fragments; xeno's progress contract replaces the displayed argument buffer.
195
+ // Keep buffers per content block so interleaved calls and retries cannot share partial JSON.
196
+ const toolArguments = new Map<number, string>();
197
+ for await (const event of APIS[model.api].streamSimple(model, normalizeContext(piContext(frozen, messages, this.config.spec)), options)) {
198
+ switch (event.type) {
199
+ case "text_delta": onDelta?.({ kind: "text", text: event.delta }); break;
200
+ case "thinking_delta": onDelta?.({ kind: "thinking", text: event.delta }); break;
201
+ case "toolcall_start": {
202
+ const block = event.partial.content[event.contentIndex];
203
+ toolArguments.set(event.contentIndex, "");
204
+ if (block?.type === "toolCall") onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: "" }); break;
205
+ }
206
+ case "toolcall_delta": {
207
+ const block = event.partial.content[event.contentIndex];
208
+ const argumentsText = (toolArguments.get(event.contentIndex) ?? "") + event.delta;
209
+ toolArguments.set(event.contentIndex, argumentsText);
210
+ if (block?.type === "toolCall") onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: argumentsText }); break;
211
+ }
212
+ case "toolcall_end": {
213
+ const block = event.toolCall;
214
+ onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: JSON.stringify(block.arguments) });
215
+ toolArguments.delete(event.contentIndex); break;
216
+ }
217
+ case "done": completed = event.message; break;
218
+ case "error":
219
+ if (event.reason === "aborted" || signal?.aborted) throw new DOMException("Request aborted", "AbortError");
220
+ status ??= nativeStatus(event.error.errorMessage ?? "");
221
+ if (status && status >= 400) throw new HttpError(status, responseHeaders, event.error.errorMessage ?? "Provider request failed");
222
+ throw new StreamError(event.error.errorMessage ?? "Provider stream failed");
223
+ case "start": case "text_start": case "text_end": case "thinking_start": case "thinking_end": break;
224
+ default: assertNever(event, "pi stream event");
225
+ }
226
+ }
227
+ signal?.throwIfAborted();
228
+ if (!completed || completed.stopReason === "deferred") throw new StreamError("Provider did not complete a synchronous agent turn");
229
+ // The SDK has already classified native STOP/end_turn/toolUse/length values. The loop consumes
230
+ // xeno's closed completion vocabulary, never a raw native spelling that could skip tool execution.
231
+ return { blocks: piBlocks(completed), stopReason: completed.stopReason === "length" ? "max_tokens" : completed.stopReason === "toolUse" ? "tool_use" : "end_turn",
232
+ usage: { input_tokens: completed.usage.input, output_tokens: completed.usage.output, cache_read_input_tokens: completed.usage.cacheRead,
233
+ cache_creation_input_tokens: completed.usage.cacheWrite, node, ms: Date.now() - started } };
234
+ }
235
+ }
@@ -0,0 +1,26 @@
1
+ /** Display updates are transient; only the completed operation may become a durable result. */
2
+ export async function* withProgress<T, E>(start: (emit: (event: E) => void) => Promise<T>): AsyncGenerator<E, T> {
3
+ const queue: E[] = [];
4
+ let wake: (() => void) | undefined;
5
+ let accepting = true;
6
+ const box: { result?: { ok: true; value: T } | { ok: false; error: unknown } } = {};
7
+ const pending = start(event => { if (accepting) queue.push(event); wake?.(); }).then(
8
+ value => { box.result = { ok: true, value }; wake?.(); },
9
+ error => { box.result = { ok: false, error }; wake?.(); },
10
+ );
11
+ try {
12
+ while (box.result === undefined || queue.length) {
13
+ if (queue.length) { yield queue.shift()!; continue; }
14
+ await new Promise<void>(resolve => { wake = resolve; });
15
+ wake = undefined;
16
+ }
17
+ } finally {
18
+ accepting = false;
19
+ queue.length = 0;
20
+ await pending;
21
+ }
22
+ await pending;
23
+ if (!box.result) throw new Error("operation settled without a result");
24
+ if (!box.result.ok) throw box.result.error;
25
+ return box.result.value;
26
+ }
@@ -0,0 +1,21 @@
1
+ import type { ModelSpec } from "./model.ts";
2
+
3
+ /** Secrets are not model metadata. Enumerating/serializing a model cannot expose config values. */
4
+ export interface ProviderAccess {
5
+ apiKey?: string;
6
+ headers?: Record<string, string | null>;
7
+ env?: Record<string, string>;
8
+ auth?: false | "ambient";
9
+ authHeader?: boolean | string;
10
+ options?: Record<string, unknown>;
11
+ body?: Record<string, unknown>;
12
+ invalid?: string;
13
+ }
14
+ const access = new WeakMap<ModelSpec, ProviderAccess>();
15
+ export function setProviderAccess(model: ModelSpec, value: ProviderAccess): void { access.set(model, value); }
16
+ export function providerAccess(model: ModelSpec): ProviderAccess { return access.get(model) ?? {}; }
17
+ export function hasProviderAccess(model: ModelSpec): boolean { return access.has(model); }
18
+ export function copyProviderAccess(from: ModelSpec, to: ModelSpec): void {
19
+ const value = access.get(from);
20
+ if (value) access.set(to, value);
21
+ }
@@ -0,0 +1,29 @@
1
+ /** Protocol vocabulary is local; SDK types and IO never enter core (D13). */
2
+ export const PROVIDER_APIS = [
3
+ "anthropic-messages", "openai-completions", "openai-responses", "google-generative-ai",
4
+ "google-vertex", "bedrock-converse-stream", "azure-openai-responses", "openai-codex-responses",
5
+ "mistral-conversations", "pi-messages",
6
+ ] as const;
7
+ export type ProviderApi = typeof PROVIDER_APIS[number];
8
+ export function isProviderApi(value: unknown): value is ProviderApi {
9
+ return typeof value === "string" && (PROVIDER_APIS as readonly string[]).includes(value);
10
+ }
11
+
12
+ /** SDK dispatch from OpenCode's model-level `provider.npm` before provider defaults. */
13
+ export const SDK_APIS: Readonly<Record<string, ProviderApi>> = {
14
+ "@ai-sdk/anthropic": "anthropic-messages",
15
+ "@ai-sdk/openai": "openai-responses",
16
+ "@ai-sdk/openai-compatible": "openai-completions",
17
+ "@openrouter/ai-sdk-provider": "openai-completions",
18
+ "@ai-sdk/groq": "openai-completions",
19
+ "@ai-sdk/cerebras": "openai-completions",
20
+ "@ai-sdk/deepinfra": "openai-completions",
21
+ "@ai-sdk/togetherai": "openai-completions",
22
+ "@ai-sdk/xai": "openai-completions",
23
+ "@ai-sdk/mistral": "mistral-conversations",
24
+ "@ai-sdk/google": "google-generative-ai",
25
+ "@ai-sdk/google-vertex": "google-vertex",
26
+ "@ai-sdk/amazon-bedrock": "bedrock-converse-stream",
27
+ "@ai-sdk/azure": "azure-openai-responses",
28
+ "@ai-sdk/gateway": "anthropic-messages",
29
+ };