@mcoda/codali 0.1.134 → 0.1.135-codali.4

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 (230) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/ORCHESTRATION.md +387 -0
  3. package/README.md +103 -1
  4. package/config/alternate-local.example.json +49 -0
  5. package/config/docdex-runtime.json +21 -0
  6. package/config/orchestration.schema.json +548 -0
  7. package/config/raijin-suku.example.json +172 -0
  8. package/config/terminal-long-scenarios.v1.json +70 -0
  9. package/dist/agents/RoleResolution.d.ts.map +1 -1
  10. package/dist/agents/RoleResolution.js +4 -4
  11. package/dist/api/CodaliApi.d.ts +16 -0
  12. package/dist/api/CodaliApi.d.ts.map +1 -1
  13. package/dist/api/CodaliApi.js +39 -10
  14. package/dist/cli/AskCommand.d.ts +3 -0
  15. package/dist/cli/AskCommand.d.ts.map +1 -1
  16. package/dist/cli/AskCommand.js +76 -13
  17. package/dist/cli/RunCommand.d.ts +4 -0
  18. package/dist/cli/RunCommand.d.ts.map +1 -1
  19. package/dist/cli/RunCommand.js +110 -24
  20. package/dist/cli/TerminalCommand.d.ts +35 -0
  21. package/dist/cli/TerminalCommand.d.ts.map +1 -0
  22. package/dist/cli/TerminalCommand.js +616 -0
  23. package/dist/cli/TerminalCredentials.d.ts +3 -0
  24. package/dist/cli/TerminalCredentials.d.ts.map +1 -0
  25. package/dist/cli/TerminalCredentials.js +89 -0
  26. package/dist/cli/TerminalInput.d.ts +24 -0
  27. package/dist/cli/TerminalInput.d.ts.map +1 -0
  28. package/dist/cli/TerminalInput.js +98 -0
  29. package/dist/cli/TerminalText.d.ts +10 -0
  30. package/dist/cli/TerminalText.d.ts.map +1 -0
  31. package/dist/cli/TerminalText.js +53 -0
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +33 -4
  34. package/dist/cognitive/ConfiguredPipeline.d.ts +34 -0
  35. package/dist/cognitive/ConfiguredPipeline.d.ts.map +1 -0
  36. package/dist/cognitive/ConfiguredPipeline.js +50 -0
  37. package/dist/cognitive/ValidationRunner.d.ts +4 -0
  38. package/dist/cognitive/ValidationRunner.d.ts.map +1 -1
  39. package/dist/cognitive/ValidationRunner.js +18 -41
  40. package/dist/config/NodeOrchestration.d.ts +21 -0
  41. package/dist/config/NodeOrchestration.d.ts.map +1 -0
  42. package/dist/config/NodeOrchestration.js +54 -0
  43. package/dist/config/OrchestrationConfig.d.ts +100 -0
  44. package/dist/config/OrchestrationConfig.d.ts.map +1 -0
  45. package/dist/config/OrchestrationConfig.js +245 -0
  46. package/dist/connectors/mcp/McpClient.d.ts +3 -1
  47. package/dist/connectors/mcp/McpClient.d.ts.map +1 -1
  48. package/dist/connectors/mcp/McpClient.js +3 -2
  49. package/dist/connectors/mcp/McpServerRegistry.d.ts +2 -2
  50. package/dist/connectors/mcp/McpServerRegistry.d.ts.map +1 -1
  51. package/dist/connectors/mcp/McpServerRegistry.js +39 -16
  52. package/dist/connectors/mcp/McpToolAdapter.js +2 -2
  53. package/dist/connectors/media/ConfiguredMediaTools.d.ts +6 -0
  54. package/dist/connectors/media/ConfiguredMediaTools.d.ts.map +1 -0
  55. package/dist/connectors/media/ConfiguredMediaTools.js +41 -0
  56. package/dist/connectors/media/ImageGenerationTool.js +3 -3
  57. package/dist/connectors/media/MediaArtifactReceipt.d.ts +5 -0
  58. package/dist/connectors/media/MediaArtifactReceipt.d.ts.map +1 -0
  59. package/dist/connectors/media/MediaArtifactReceipt.js +65 -0
  60. package/dist/connectors/media/MediaJobStore.d.ts +68 -0
  61. package/dist/connectors/media/MediaJobStore.d.ts.map +1 -0
  62. package/dist/connectors/media/MediaJobStore.js +199 -0
  63. package/dist/connectors/media/MediaJobWorker.d.ts +2 -0
  64. package/dist/connectors/media/MediaJobWorker.d.ts.map +1 -0
  65. package/dist/connectors/media/MediaJobWorker.js +65 -0
  66. package/dist/connectors/media/RemoteMediaJob.d.ts +6 -0
  67. package/dist/connectors/media/RemoteMediaJob.d.ts.map +1 -0
  68. package/dist/connectors/media/RemoteMediaJob.js +80 -0
  69. package/dist/docdex/DocdexClient.d.ts +18 -1
  70. package/dist/docdex/DocdexClient.d.ts.map +1 -1
  71. package/dist/docdex/DocdexClient.js +135 -19
  72. package/dist/docdex/ManagedDocdex.d.ts +29 -0
  73. package/dist/docdex/ManagedDocdex.d.ts.map +1 -0
  74. package/dist/docdex/ManagedDocdex.js +215 -0
  75. package/dist/docdex/ManagedRuntime.d.ts +28 -0
  76. package/dist/docdex/ManagedRuntime.d.ts.map +1 -0
  77. package/dist/docdex/ManagedRuntime.js +212 -0
  78. package/dist/eval/EvaluationProcess.d.ts +13 -0
  79. package/dist/eval/EvaluationProcess.d.ts.map +1 -0
  80. package/dist/eval/EvaluationProcess.js +76 -0
  81. package/dist/eval/LongWorkflowJourney.d.ts +34 -0
  82. package/dist/eval/LongWorkflowJourney.d.ts.map +1 -0
  83. package/dist/eval/LongWorkflowJourney.js +243 -0
  84. package/dist/eval/PlanningCorpus.d.ts +45 -0
  85. package/dist/eval/PlanningCorpus.d.ts.map +1 -0
  86. package/dist/eval/PlanningCorpus.js +189 -0
  87. package/dist/eval/PlanningCorpusRunner.d.ts +13 -0
  88. package/dist/eval/PlanningCorpusRunner.d.ts.map +1 -0
  89. package/dist/eval/PlanningCorpusRunner.js +89 -0
  90. package/dist/eval/TerminalCorpusRunner.d.ts +36 -0
  91. package/dist/eval/TerminalCorpusRunner.d.ts.map +1 -0
  92. package/dist/eval/TerminalCorpusRunner.js +222 -0
  93. package/dist/eval/TerminalTaskCorpus.d.ts +26 -0
  94. package/dist/eval/TerminalTaskCorpus.d.ts.map +1 -0
  95. package/dist/eval/TerminalTaskCorpus.js +105 -0
  96. package/dist/eval/TerminalWorkflowCorpus.d.ts +11 -0
  97. package/dist/eval/TerminalWorkflowCorpus.d.ts.map +1 -0
  98. package/dist/eval/TerminalWorkflowCorpus.js +116 -0
  99. package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts +4 -0
  100. package/dist/eval/fixtures/TerminalRecoveryJourney.d.ts.map +1 -0
  101. package/dist/eval/fixtures/TerminalRecoveryJourney.js +132 -0
  102. package/dist/gateway/CodaliGateway.d.ts +2 -0
  103. package/dist/gateway/CodaliGateway.d.ts.map +1 -1
  104. package/dist/gateway/CodaliGateway.js +11 -1
  105. package/dist/gateway/ConfiguredGateway.d.ts +23 -0
  106. package/dist/gateway/ConfiguredGateway.d.ts.map +1 -0
  107. package/dist/gateway/ConfiguredGateway.js +44 -0
  108. package/dist/gateway/GatewayTracer.d.ts +1 -1
  109. package/dist/gateway/GatewayTracer.d.ts.map +1 -1
  110. package/dist/gateway/GatewayTracer.js +2 -27
  111. package/dist/index.d.ts +23 -0
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +12 -0
  114. package/dist/permissions/PermissionGate.d.ts +44 -0
  115. package/dist/permissions/PermissionGate.d.ts.map +1 -0
  116. package/dist/permissions/PermissionGate.js +97 -0
  117. package/dist/providers/ConfiguredEmbeddings.d.ts +43 -0
  118. package/dist/providers/ConfiguredEmbeddings.d.ts.map +1 -0
  119. package/dist/providers/ConfiguredEmbeddings.js +87 -0
  120. package/dist/providers/ConfiguredModelScope.d.ts +69 -0
  121. package/dist/providers/ConfiguredModelScope.d.ts.map +1 -0
  122. package/dist/providers/ConfiguredModelScope.js +113 -0
  123. package/dist/providers/ConfiguredProvider.d.ts +79 -0
  124. package/dist/providers/ConfiguredProvider.d.ts.map +1 -0
  125. package/dist/providers/ConfiguredProvider.js +238 -0
  126. package/dist/providers/ModelServiceError.d.ts +10 -0
  127. package/dist/providers/ModelServiceError.d.ts.map +1 -0
  128. package/dist/providers/ModelServiceError.js +45 -0
  129. package/dist/providers/MswarmWorkerProvider.d.ts.map +1 -1
  130. package/dist/providers/MswarmWorkerProvider.js +4 -0
  131. package/dist/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
  132. package/dist/providers/OpenAiCompatibleProvider.js +91 -50
  133. package/dist/providers/ProviderTypes.d.ts +12 -0
  134. package/dist/providers/ProviderTypes.d.ts.map +1 -1
  135. package/dist/runtime/CodaliJobRuntime.d.ts +6 -0
  136. package/dist/runtime/CodaliJobRuntime.d.ts.map +1 -1
  137. package/dist/runtime/CodaliJobRuntime.js +36 -1
  138. package/dist/runtime/CodaliRuntime.d.ts +28 -0
  139. package/dist/runtime/CodaliRuntime.d.ts.map +1 -1
  140. package/dist/runtime/CodaliRuntime.js +69 -1
  141. package/dist/runtime/CompletionReport.d.ts +5 -0
  142. package/dist/runtime/CompletionReport.d.ts.map +1 -0
  143. package/dist/runtime/CompletionReport.js +12 -0
  144. package/dist/runtime/ProgressTracker.d.ts +17 -0
  145. package/dist/runtime/ProgressTracker.d.ts.map +1 -0
  146. package/dist/runtime/ProgressTracker.js +27 -0
  147. package/dist/runtime/RequestedTestEvidence.d.ts +12 -0
  148. package/dist/runtime/RequestedTestEvidence.d.ts.map +1 -0
  149. package/dist/runtime/RequestedTestEvidence.js +16 -0
  150. package/dist/runtime/RequiredRetrieval.d.ts +13 -0
  151. package/dist/runtime/RequiredRetrieval.d.ts.map +1 -0
  152. package/dist/runtime/RequiredRetrieval.js +25 -0
  153. package/dist/runtime/ReviewSource.d.ts +6 -0
  154. package/dist/runtime/ReviewSource.d.ts.map +1 -0
  155. package/dist/runtime/ReviewSource.js +38 -0
  156. package/dist/runtime/Runner.d.ts +7 -0
  157. package/dist/runtime/Runner.d.ts.map +1 -1
  158. package/dist/runtime/Runner.js +215 -171
  159. package/dist/runtime/SecretRedaction.d.ts +11 -0
  160. package/dist/runtime/SecretRedaction.d.ts.map +1 -0
  161. package/dist/runtime/SecretRedaction.js +85 -0
  162. package/dist/runtime/SessionController.d.ts +148 -0
  163. package/dist/runtime/SessionController.d.ts.map +1 -0
  164. package/dist/runtime/SessionController.js +1357 -0
  165. package/dist/runtime/StructuralEvidence.d.ts +22 -0
  166. package/dist/runtime/StructuralEvidence.d.ts.map +1 -0
  167. package/dist/runtime/StructuralEvidence.js +87 -0
  168. package/dist/runtime/TaskReviewContext.d.ts +34 -0
  169. package/dist/runtime/TaskReviewContext.d.ts.map +1 -0
  170. package/dist/runtime/TaskReviewContext.js +34 -0
  171. package/dist/runtime/ValidationOutput.d.ts +5 -0
  172. package/dist/runtime/ValidationOutput.d.ts.map +1 -0
  173. package/dist/runtime/ValidationOutput.js +32 -0
  174. package/dist/runtime/VerificationCommand.d.ts +5 -0
  175. package/dist/runtime/VerificationCommand.d.ts.map +1 -0
  176. package/dist/runtime/VerificationCommand.js +76 -0
  177. package/dist/runtime/WorkStatusPage.d.ts +15 -0
  178. package/dist/runtime/WorkStatusPage.d.ts.map +1 -0
  179. package/dist/runtime/WorkStatusPage.js +58 -0
  180. package/dist/runtime/WorkspaceLedger.d.ts +13 -0
  181. package/dist/runtime/WorkspaceLedger.d.ts.map +1 -0
  182. package/dist/runtime/WorkspaceLedger.js +145 -0
  183. package/dist/runtime/WorkspaceLock.d.ts.map +1 -1
  184. package/dist/runtime/WorkspaceLock.js +20 -17
  185. package/dist/runtime/WorkspaceSnapshot.d.ts +13 -0
  186. package/dist/runtime/WorkspaceSnapshot.d.ts.map +1 -0
  187. package/dist/runtime/WorkspaceSnapshot.js +88 -0
  188. package/dist/session/AtomicState.d.ts +13 -0
  189. package/dist/session/AtomicState.d.ts.map +1 -0
  190. package/dist/session/AtomicState.js +123 -0
  191. package/dist/session/SessionStoragePath.d.ts +15 -0
  192. package/dist/session/SessionStoragePath.d.ts.map +1 -0
  193. package/dist/session/SessionStoragePath.js +89 -0
  194. package/dist/session/SessionStore.d.ts +9 -0
  195. package/dist/session/SessionStore.d.ts.map +1 -1
  196. package/dist/session/SessionStore.js +181 -47
  197. package/dist/session/WorkState.d.ts +23 -0
  198. package/dist/session/WorkState.d.ts.map +1 -0
  199. package/dist/session/WorkState.js +367 -0
  200. package/dist/session/WorkTypes.d.ts +94 -0
  201. package/dist/session/WorkTypes.d.ts.map +1 -0
  202. package/dist/session/WorkTypes.js +1 -0
  203. package/dist/tools/ToolRegistry.d.ts +2 -0
  204. package/dist/tools/ToolRegistry.d.ts.map +1 -1
  205. package/dist/tools/ToolRegistry.js +12 -2
  206. package/dist/tools/ToolTypes.d.ts +16 -0
  207. package/dist/tools/ToolTypes.d.ts.map +1 -1
  208. package/dist/tools/docdex/DocdexTools.d.ts +2 -0
  209. package/dist/tools/docdex/DocdexTools.d.ts.map +1 -1
  210. package/dist/tools/docdex/DocdexTools.js +17 -2
  211. package/dist/tools/filesystem/FileTools.d.ts.map +1 -1
  212. package/dist/tools/filesystem/FileTools.js +30 -10
  213. package/dist/tools/shell/AsyncProcess.d.ts +19 -0
  214. package/dist/tools/shell/AsyncProcess.d.ts.map +1 -0
  215. package/dist/tools/shell/AsyncProcess.js +94 -0
  216. package/dist/tools/shell/DarwinRuntimeLibraries.d.ts +13 -0
  217. package/dist/tools/shell/DarwinRuntimeLibraries.d.ts.map +1 -0
  218. package/dist/tools/shell/DarwinRuntimeLibraries.js +130 -0
  219. package/dist/tools/shell/HostToolchain.d.ts +19 -0
  220. package/dist/tools/shell/HostToolchain.d.ts.map +1 -0
  221. package/dist/tools/shell/HostToolchain.js +266 -0
  222. package/dist/tools/shell/IsolatedExecutor.d.ts +15 -0
  223. package/dist/tools/shell/IsolatedExecutor.d.ts.map +1 -0
  224. package/dist/tools/shell/IsolatedExecutor.js +138 -0
  225. package/dist/tools/shell/ShellTool.d.ts.map +1 -1
  226. package/dist/tools/shell/ShellTool.js +9 -29
  227. package/dist/tools/shell/ShellWorkingDirectory.d.ts +3 -0
  228. package/dist/tools/shell/ShellWorkingDirectory.d.ts.map +1 -0
  229. package/dist/tools/shell/ShellWorkingDirectory.js +30 -0
  230. package/package.json +27 -24
@@ -0,0 +1,238 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { CryptoHelper, PathHelper, mswarmClientProductHeaders } from "@mcoda/shared";
4
+ import { resolveCredential } from "../runcontext/CredentialFile.js";
5
+ import { resolveBinding } from "../config/OrchestrationConfig.js";
6
+ import { OpenAiCompatibleProvider } from "./OpenAiCompatibleProvider.js";
7
+ import { ModelServiceError } from "./ModelServiceError.js";
8
+ import { registerSecretValue } from "../runtime/SecretRedaction.js";
9
+ export const normalizeModelCaller = (caller = {}) => {
10
+ const clientIdentity = caller.clientIdentity?.trim();
11
+ const clientProduct = caller.clientProduct?.trim().toLowerCase() ?? "codali";
12
+ if (clientIdentity !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(clientIdentity))
13
+ throw new Error("Invalid model client identity");
14
+ if (!/^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$/.test(clientProduct))
15
+ throw new Error("Invalid model client product");
16
+ return { ...(clientIdentity ? { clientIdentity } : {}), clientProduct };
17
+ };
18
+ const modelCallerHeaders = (caller) => ({
19
+ ...mswarmClientProductHeaders(caller.clientProduct),
20
+ ...(caller.clientIdentity ? { "x-mswarm-client-identity": caller.clientIdentity, "x-mswarm-client": caller.clientIdentity } : {}),
21
+ });
22
+ const record = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
23
+ /** Discovery failed before inference; a caller may release its local token reservation. */
24
+ export class NativeReasoningTransportError extends Error {
25
+ constructor(message) { super(message); this.name = "NativeReasoningTransportError"; }
26
+ }
27
+ export const providerCredential = async (provider) => {
28
+ if (!provider.credentialRef)
29
+ return undefined;
30
+ const name = provider.credentialRef.slice(4);
31
+ const direct = resolveCredential(name);
32
+ if (direct) {
33
+ registerSecretValue(direct);
34
+ return direct;
35
+ }
36
+ if (provider.adapter === "mswarm" && resolveCredential("CODALI_MSWARM_LOGGED_OUT") === "1")
37
+ throw new Error("Logged out; use codali login");
38
+ // Migrate by reading the existing user store; never read repository credentials.
39
+ if (provider.adapter === "mswarm" && name === "MCODA_MSWARM_API_KEY") {
40
+ try {
41
+ const config = JSON.parse(await readFile(PathHelper.getGlobalConfigPath(), "utf8"));
42
+ const existing = config.mswarm;
43
+ if (existing?.encryptedApiKey && new URL(existing.baseUrl ?? "https://api.mswarm.org").origin === new URL(provider.baseUrl).origin) {
44
+ const key = await CryptoHelper.decryptSecret(existing.encryptedApiKey);
45
+ registerSecretValue(key);
46
+ return key;
47
+ }
48
+ }
49
+ catch (error) {
50
+ if (error.code !== "ENOENT")
51
+ throw new Error("Stored mSwarm credentials could not be loaded; use codali login");
52
+ }
53
+ }
54
+ throw new Error(`Missing credential ${name}; use codali login or set the environment variable`);
55
+ };
56
+ export class ModelServiceClient {
57
+ constructor(provider, credentialOverride, caller) {
58
+ this.provider = provider;
59
+ this.credentialOverride = credentialOverride;
60
+ this.caller = normalizeModelCaller(caller);
61
+ if (credentialOverride)
62
+ registerSecretValue(credentialOverride);
63
+ }
64
+ credential() { return this.credentialOverride === undefined ? providerCredential(this.provider) : Promise.resolve(this.credentialOverride); }
65
+ async json(url, options = {}) {
66
+ if (new URL(url).origin !== new URL(this.provider.baseUrl).origin)
67
+ throw new Error("Refusing credentials to an unconfigured origin");
68
+ const key = await this.credential();
69
+ const controller = new AbortController();
70
+ const abort = () => controller.abort(options.signal?.reason);
71
+ options.signal?.throwIfAborted();
72
+ options.signal?.addEventListener("abort", abort, { once: true });
73
+ const timer = setTimeout(() => controller.abort(new Error("Service request timed out")), options.timeoutMs ?? 30000);
74
+ try {
75
+ const response = await fetch(url, {
76
+ redirect: "error", method: options.method ?? "GET", signal: controller.signal,
77
+ headers: { accept: "application/json", ...(key ? this.provider.adapter === "mswarm" ? { "x-api-key": key } : { authorization: `Bearer ${key}` } : {}), ...(this.provider.adapter === "mswarm" ? modelCallerHeaders(this.caller) : {}), ...(options.body ? { "content-type": "application/json" } : {}) },
78
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
79
+ });
80
+ if (!response.ok)
81
+ throw await ModelServiceError.fromResponse(response);
82
+ if (!response.body)
83
+ throw new Error("Empty service response");
84
+ const reader = response.body.getReader();
85
+ const chunks = [];
86
+ let size = 0;
87
+ try {
88
+ while (true) {
89
+ const next = await reader.read();
90
+ if (next.done)
91
+ break;
92
+ size += next.value.byteLength;
93
+ if (size > 96 * 1024 * 1024) {
94
+ await reader.cancel();
95
+ throw new Error("Service response exceeds size limit");
96
+ }
97
+ chunks.push(next.value);
98
+ }
99
+ }
100
+ finally {
101
+ reader.releaseLock();
102
+ }
103
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
104
+ }
105
+ finally {
106
+ clearTimeout(timer);
107
+ options.signal?.removeEventListener("abort", abort);
108
+ }
109
+ }
110
+ async catalog(signal, refresh = false) {
111
+ const credential = await this.credential();
112
+ const key = createHash("sha256").update(credential ?? "anonymous").digest("hex");
113
+ if (!refresh && this.cache?.key === key && this.cache.expires > Date.now())
114
+ return this.cache.entries;
115
+ const base = new URL(this.provider.baseUrl);
116
+ const url = this.provider.catalogUrl ?? (this.provider.adapter === "mswarm" ? new URL("/v1/swarm/self-hosted/agents", base).toString() : `${this.provider.baseUrl.replace(/\/$/, "")}/models`);
117
+ const payload = record(await this.json(url, { signal }));
118
+ const items = payload.agents ?? payload.data;
119
+ if (!Array.isArray(items))
120
+ throw new Error("Invalid model catalog response");
121
+ const entries = items.map(item => {
122
+ const raw = record(item);
123
+ const aliases = [raw.slug, raw.public_model_id, raw.default_model, raw.id].filter((v) => typeof v === "string" && !!v);
124
+ return { id: aliases[0] ?? "", aliases, backend: raw.adapter === "strata-local" ? "strata" : typeof raw.backend === "string" ? raw.backend : typeof raw.adapter === "string" ? raw.adapter : undefined,
125
+ health: String(raw.health_status ?? "unknown"), supportsTools: raw.supports_tools === true,
126
+ contextWindow: typeof raw.context_window === "number" ? raw.context_window : undefined,
127
+ operations: Array.isArray(raw.operations) ? raw.operations.map(v => typeof v === "string" ? v : String(record(v).type ?? "")) : [], raw };
128
+ }).filter(x => x.id);
129
+ this.cache = { key, expires: Date.now() + 30000, entries };
130
+ return entries;
131
+ }
132
+ async usage(signal) {
133
+ if (this.provider.adapter !== "mswarm" && !this.provider.usageUrl)
134
+ return { available: false };
135
+ return this.json(this.provider.usageUrl ?? new URL("/v1/swarm/runtime/usage-limits", this.provider.baseUrl).toString(), { signal });
136
+ }
137
+ /** The proxy and selected node must both advertise this transport contract.
138
+ * A model's supports_reasoning flag alone says nothing about request forwarding. */
139
+ async requireNativeReasoning(modelId, parameters, signal) {
140
+ if (this.provider.adapter !== "mswarm" || parameters.length === 0)
141
+ return;
142
+ const endpoint = new URL(this.provider.baseUrl);
143
+ endpoint.pathname = endpoint.pathname.replace(/\/$/, "");
144
+ endpoint.pathname = ["", "/v1"].includes(endpoint.pathname)
145
+ ? "/v1/swarm/self-hosted/openai/models" : `${endpoint.pathname}/models`;
146
+ const payload = record(await this.json(endpoint.href, { signal }).catch(error => {
147
+ throw new NativeReasoningTransportError(`Native reasoning preflight failed before inference: ${error instanceof Error ? error.message : String(error)}`);
148
+ }));
149
+ const models = Array.isArray(payload.data) ? payload.data.map(record) : [];
150
+ const model = models.find(item => [item.id, item.agent_slug, item.remote_slug].includes(modelId));
151
+ const contract = record(model?.native_reasoning);
152
+ const supported = Array.isArray(contract.parameters) ? contract.parameters : [];
153
+ if (contract.transport_version !== 1 || parameters.some(parameter => !supported.includes(parameter))) {
154
+ throw new NativeReasoningTransportError(`Native reasoning transport is unavailable for ${modelId}; upgrade the mSwarm proxy and selected node, or remove unsupported native reasoning settings from the configuration`);
155
+ }
156
+ }
157
+ }
158
+ export class ConfiguredProvider {
159
+ constructor(resolved, binding = "lead", options = {}) {
160
+ this.resolved = resolved;
161
+ this.binding = binding;
162
+ this.options = options;
163
+ this.name = "configured";
164
+ this.supportsToolCalls = true;
165
+ this.clients = new Map();
166
+ this.caller = normalizeModelCaller(options.caller);
167
+ this.credentials = Object.freeze(Object.assign(Object.create(null), options.credentials));
168
+ for (const [id, credential] of Object.entries(this.credentials)) {
169
+ if (!Object.prototype.hasOwnProperty.call(resolved.config.providers, id) || typeof credential !== "string" || !credential.trim())
170
+ throw new Error("Invalid host provider credential mapping");
171
+ registerSecretValue(credential);
172
+ }
173
+ }
174
+ client(providerId) {
175
+ const provider = this.resolved.config.providers[providerId];
176
+ if (!provider)
177
+ throw new Error(`Unknown configured provider: ${providerId}`);
178
+ if (!this.clients.has(providerId))
179
+ this.clients.set(providerId, new ModelServiceClient(provider, this.credentials[providerId], this.caller));
180
+ return this.clients.get(providerId);
181
+ }
182
+ async select(operation, signal, requireTools = false) {
183
+ const candidates = resolveBinding(this.resolved.config, this.binding, operation);
184
+ const reasons = [];
185
+ for (const candidate of candidates) {
186
+ const { id, model } = candidate;
187
+ const catalog = await this.client(model.provider).catalog(signal);
188
+ const actual = catalog.find(entry => entry.aliases.includes(model.catalogId));
189
+ if (!actual) {
190
+ reasons.push(`${id}: not entitled/advertised`);
191
+ continue;
192
+ }
193
+ if (!["healthy", "unknown", "ready"].includes(actual.health)) {
194
+ reasons.push(`${id}: ${actual.health}`);
195
+ continue;
196
+ }
197
+ if (model.backend && actual.backend !== model.backend) {
198
+ reasons.push(`${id}: backend identity not verified`);
199
+ continue;
200
+ }
201
+ if (actual.operations.length && !actual.operations.includes(operation)) {
202
+ reasons.push(`${id}: unsupported operation`);
203
+ continue;
204
+ }
205
+ if (requireTools && (!model.supportsTools || this.resolved.config.providers[model.provider].adapter === "mswarm" && !actual.supportsTools)) {
206
+ reasons.push(`${id}: tools unavailable`);
207
+ continue;
208
+ }
209
+ return { id, model, actual, provider: this.resolved.config.providers[model.provider] };
210
+ }
211
+ throw new Error(`No eligible configured ${this.binding} route: ${reasons.join("; ")}`);
212
+ }
213
+ async generate(request) {
214
+ request.signal?.throwIfAborted();
215
+ const { id, model, provider, actual } = await this.select("chat.completions", request.signal, !!request.tools?.length);
216
+ const invocation = { id: randomUUID(), binding: this.binding, configHash: this.resolved.hash, modelId: id, catalogId: model.catalogId, backend: actual.backend, host: model.host };
217
+ const nativeReasoning = this.options.effort ? model.reasoning?.[this.options.effort] : undefined;
218
+ if (nativeReasoning)
219
+ invocation.nativeReasoningRequest = { ...nativeReasoning };
220
+ if (nativeReasoning)
221
+ await this.client(model.provider).requireNativeReasoning(model.catalogId, Object.keys(nativeReasoning), request.signal);
222
+ const maxTokens = Math.min(request.maxTokens ?? model.maxOutputTokens, model.maxOutputTokens);
223
+ const inputEstimate = Math.ceil(JSON.stringify([request.messages, request.tools]).length / 3);
224
+ if (inputEstimate + maxTokens > Math.min(model.contextWindow, actual.contextWindow ?? model.contextWindow))
225
+ throw new Error("Context budget exceeded; checkpoint and compact before continuing");
226
+ const bounded = { ...request, maxTokens, stream: request.stream === true && model.supportsStreaming === true };
227
+ await this.options.beforeRequest?.(invocation, bounded);
228
+ const adapter = new OpenAiCompatibleProvider({
229
+ model: model.catalogId, baseUrl: provider.baseUrl, apiKey: this.credentials[model.provider] ?? await providerCredential(provider), timeoutMs: 300000,
230
+ supportsStreaming: model.supportsStreaming ?? true, supportsTools: model.supportsTools,
231
+ headers: provider.adapter === "mswarm" ? modelCallerHeaders(this.caller) : undefined, retryTransient: false,
232
+ extraBody: nativeReasoning,
233
+ });
234
+ const result = await adapter.generate(bounded);
235
+ await this.options.afterRequest?.(invocation, result);
236
+ return { ...result, route: invocation };
237
+ }
238
+ }
@@ -0,0 +1,10 @@
1
+ export type ModelServiceErrorCode = "authentication_failed" | "entitlement_missing" | "quota_exhausted" | "rate_limited" | "service_unavailable" | "model_unavailable" | "invalid_request";
2
+ /** Transport diagnostics contain stable classifications, never an upstream body. */
3
+ export declare class ModelServiceError extends Error {
4
+ readonly code: ModelServiceErrorCode;
5
+ readonly status: number;
6
+ readonly retryAfterSeconds?: number | undefined;
7
+ constructor(code: ModelServiceErrorCode, status: number, retryAfterSeconds?: number | undefined);
8
+ static fromResponse(response: Response): Promise<ModelServiceError>;
9
+ }
10
+ //# sourceMappingURL=ModelServiceError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelServiceError.d.ts","sourceRoot":"","sources":["../../src/providers/ModelServiceError.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAE3L,oFAAoF;AACpF,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,IAAI,EAAE,qBAAqB;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM;gBAAzF,IAAI,EAAE,qBAAqB,EAAW,MAAM,EAAE,MAAM,EAAW,iBAAiB,CAAC,EAAE,MAAM,YAAA;WAKjG,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAoB1E"}
@@ -0,0 +1,45 @@
1
+ /** Transport diagnostics contain stable classifications, never an upstream body. */
2
+ export class ModelServiceError extends Error {
3
+ constructor(code, status, retryAfterSeconds) {
4
+ super(`Model service HTTP ${status}: ${code}${code === "authentication_failed" ? "; authenticate with codali login" : ""}`);
5
+ this.code = code;
6
+ this.status = status;
7
+ this.retryAfterSeconds = retryAfterSeconds;
8
+ this.name = "ModelServiceError";
9
+ }
10
+ static async fromResponse(response) {
11
+ let serverCode = "";
12
+ if (response.body) {
13
+ const reader = response.body.getReader();
14
+ const chunks = [];
15
+ let bytes = 0;
16
+ try {
17
+ while (true) {
18
+ const part = await reader.read();
19
+ if (part.done)
20
+ break;
21
+ bytes += part.value.byteLength;
22
+ if (bytes > 8192)
23
+ break;
24
+ chunks.push(part.value);
25
+ }
26
+ const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
27
+ if (typeof (payload.error?.code ?? payload.code) === "string")
28
+ serverCode = String(payload.error?.code ?? payload.code);
29
+ }
30
+ catch { /* The status still supplies a safe classification. */ }
31
+ finally {
32
+ await reader.cancel().catch(() => undefined);
33
+ reader.releaseLock();
34
+ }
35
+ }
36
+ const code = response.status === 401 ? "authentication_failed"
37
+ : response.status === 403 ? "entitlement_missing"
38
+ : ["quota_exceeded", "quota_exhausted", "insufficient_quota", "credits_exhausted", "insufficient_credits"].includes(serverCode) || response.status === 402 ? "quota_exhausted"
39
+ : response.status === 429 ? "rate_limited"
40
+ : response.status === 404 ? "model_unavailable"
41
+ : response.status >= 500 ? "service_unavailable" : "invalid_request";
42
+ const retry = Number(response.headers.get("retry-after"));
43
+ return new ModelServiceError(code, response.status, response.headers.has("retry-after") && Number.isFinite(retry) && retry >= 0 ? Math.min(retry, 3600) : undefined);
44
+ }
45
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"MswarmWorkerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/MswarmWorkerProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,eAAe,EACf,gBAAgB,EAEjB,MAAM,oBAAoB,CAAC;AA8C5B,qBAAa,oBAAqB,YAAW,QAAQ;IAQvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,SAAS;IAEnC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEH,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAWvG,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA+DpE"}
1
+ {"version":3,"file":"MswarmWorkerProvider.d.ts","sourceRoot":"","sources":["../../src/providers/MswarmWorkerProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,eAAe,EACf,gBAAgB,EAEjB,MAAM,oBAAoB,CAAC;AA8C5B,qBAAa,oBAAqB,YAAW,QAAQ;IAQvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,SAAS;IAEnC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEH,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAWvG,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAmEpE"}
@@ -52,7 +52,10 @@ export class MswarmWorkerProvider {
52
52
  this.apiKey = config.apiKey;
53
53
  }
54
54
  async generate(request) {
55
+ request.signal?.throwIfAborted();
55
56
  const controller = new AbortController();
57
+ const onAbort = () => controller.abort(request.signal?.reason);
58
+ request.signal?.addEventListener("abort", onAbort, { once: true });
56
59
  const timeout = this.config.timeoutMs && this.config.timeoutMs > 0
57
60
  ? setTimeout(() => controller.abort(), this.config.timeoutMs)
58
61
  : undefined;
@@ -106,6 +109,7 @@ export class MswarmWorkerProvider {
106
109
  };
107
110
  }
108
111
  finally {
112
+ request.signal?.removeEventListener("abort", onAbort);
109
113
  if (timeout) {
110
114
  clearTimeout(timeout);
111
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OpenAiCompatibleProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OpenAiCompatibleProvider.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EAEd,eAAe,EACf,gBAAgB,EAGjB,MAAM,oBAAoB,CAAC;AA0J5B,eAAO,MAAM,uBAAuB,GAClC,KAAK,MAAM,EACX,MAAM,WAAW,EACjB,aAAa,eAAe,KAC3B,OAAO,CAAC,QAAQ,CAclB,CAAC;AAEF,qBAAa,wBAAyB,YAAW,QAAQ;IAS3C,OAAO,CAAC,MAAM;IAR1B,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,QAAQ;IAElC,IAAI,SAAuB;IAC3B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,sBAAsB,CAAoC;gBAE9C,MAAM,EAAE,cAAc;IAWpC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiPnE,OAAO,CAAC,WAAW;CA0BpB"}
1
+ {"version":3,"file":"OpenAiCompatibleProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OpenAiCompatibleProvider.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EAEd,eAAe,EACf,gBAAgB,EAGjB,MAAM,oBAAoB,CAAC;AAkK5B,eAAO,MAAM,uBAAuB,GAClC,KAAK,MAAM,EACX,MAAM,WAAW,EACjB,aAAa,eAAe,KAC3B,OAAO,CAAC,QAAQ,CAclB,CAAC;AAEF,qBAAa,wBAAyB,YAAW,QAAQ;IAS3C,OAAO,CAAC,MAAM;IAR1B,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,QAAQ;IAElC,IAAI,SAAuB;IAC3B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,sBAAsB,CAAoC;gBAE9C,MAAM,EAAE,cAAc;IAWpC,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoPnE,OAAO,CAAC,WAAW;CA0BpB"}
@@ -1,4 +1,5 @@
1
1
  import { normalizeLocalOpenAiCompatibleRunnerConfig, } from "@mcoda/shared";
2
+ import { ModelServiceError } from "./ModelServiceError.js";
2
3
  const parseToolArgs = (raw) => {
3
4
  try {
4
5
  return JSON.parse(raw);
@@ -7,6 +8,11 @@ const parseToolArgs = (raw) => {
7
8
  return raw;
8
9
  }
9
10
  };
11
+ // JSON permits lone UTF-16 surrogates, but native tokenizers often require
12
+ // Unicode scalar values. Keep their visible source spelling in model context
13
+ // instead of losing them to replacement characters. With /u, valid pairs are
14
+ // matched as one astral code point and are therefore outside this range.
15
+ const modelContextText = (value) => value.replace(/[\uD800-\uDFFF]/gu, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`);
10
16
  const normalizeBaseUrl = (baseUrl) => {
11
17
  const root = baseUrl ?? "https://api.openai.com/v1";
12
18
  return root.endsWith("/") ? root : `${root}/`;
@@ -148,6 +154,7 @@ export class OpenAiCompatibleProvider {
148
154
  this.responseFormatStrategy = normalizedLocal.config.responseFormatStrategy ?? "openai";
149
155
  }
150
156
  async generate(request) {
157
+ request.signal?.throwIfAborted();
151
158
  const baseUrl = normalizeBaseUrl(this.localRunner.baseUrl ?? this.config.baseUrl);
152
159
  const url = new URL("chat/completions", baseUrl).toString();
153
160
  const auth = this.resolveAuth();
@@ -168,7 +175,7 @@ export class OpenAiCompatibleProvider {
168
175
  : undefined;
169
176
  const messages = request.messages.map((message) => ({
170
177
  role: message.role,
171
- content: message.content,
178
+ content: modelContextText(message.content),
172
179
  name: message.name,
173
180
  tool_call_id: message.toolCallId,
174
181
  tool_calls: message.toolCalls?.map((call) => ({
@@ -176,7 +183,7 @@ export class OpenAiCompatibleProvider {
176
183
  type: "function",
177
184
  function: {
178
185
  name: call.name,
179
- arguments: typeof call.args === "string" ? call.args : JSON.stringify(call.args ?? {}),
186
+ arguments: typeof call.args === "string" ? modelContextText(call.args) : JSON.stringify(call.args ?? {}),
180
187
  },
181
188
  })),
182
189
  }));
@@ -199,7 +206,7 @@ export class OpenAiCompatibleProvider {
199
206
  type: "function",
200
207
  function: {
201
208
  name: tool.name,
202
- description: tool.description,
209
+ description: tool.description === undefined ? undefined : modelContextText(tool.description),
203
210
  parameters: tool.inputSchema ?? {},
204
211
  },
205
212
  })),
@@ -228,20 +235,23 @@ export class OpenAiCompatibleProvider {
228
235
  }
229
236
  }
230
237
  const controller = new AbortController();
238
+ const onAbort = () => controller.abort(request.signal?.reason);
239
+ request.signal?.addEventListener("abort", onAbort, { once: true });
231
240
  const timeoutMs = this.config.timeoutMs ?? 60000;
232
241
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
233
242
  try {
234
- const response = await fetchWithTransientRetry(url, {
243
+ const requestFetch = this.config.retryTransient === false ? (url, init) => fetch(url, init) : fetchWithTransientRetry;
244
+ const response = await requestFetch(url, {
235
245
  method: "POST",
236
246
  headers,
237
247
  body: JSON.stringify(body),
238
248
  signal: controller.signal,
249
+ redirect: "error",
239
250
  }, controller);
240
251
  if (!response.ok) {
241
- const errorBody = await response.text();
242
- throw new Error(`OpenAI-compatible error ${response.status}: ${errorBody}`);
252
+ throw await ModelServiceError.fromResponse(response);
243
253
  }
244
- if (request.stream) {
254
+ if (request.stream && !response.headers.get("content-type")?.includes("application/json")) {
245
255
  if (!response.body) {
246
256
  throw new Error("OpenAI-compatible streaming response missing body");
247
257
  }
@@ -249,55 +259,85 @@ export class OpenAiCompatibleProvider {
249
259
  const decoder = new TextDecoder();
250
260
  let buffer = "";
251
261
  let content = "";
262
+ let ended = false;
263
+ let finishReason;
252
264
  const toolCallMap = new Map();
253
265
  let usage;
254
- while (true) {
255
- const { value, done } = await reader.read();
256
- if (done)
257
- break;
258
- buffer += decoder.decode(value, { stream: true });
259
- const lines = buffer.split("\n");
260
- buffer = lines.pop() ?? "";
261
- for (const line of lines) {
262
- const trimmed = line.trim();
263
- if (!trimmed.startsWith("data:"))
264
- continue;
265
- const data = trimmed.slice(5).trim();
266
- if (!data || data === "[DONE]")
267
- continue;
268
- try {
269
- const chunk = JSON.parse(data);
270
- const delta = chunk.choices?.[0]?.delta;
271
- if (delta?.content) {
272
- content += delta.content;
273
- emitToken(delta.content);
274
- }
275
- if (Array.isArray(delta?.tool_calls)) {
276
- for (const call of delta.tool_calls) {
277
- const index = call.index ?? 0;
278
- const existing = toolCallMap.get(index) ?? {
279
- id: call.id ?? `call_${index + 1}`,
280
- name: "",
281
- args: "",
282
- };
283
- if (call.function?.name) {
284
- existing.name = call.function.name;
285
- }
286
- if (typeof call.function?.arguments === "string") {
287
- existing.args += call.function.arguments;
288
- }
289
- toolCallMap.set(index, existing);
290
- }
291
- }
292
- if (chunk.usage) {
293
- usage = chunk.usage;
294
- }
266
+ const consume = (line) => {
267
+ const trimmed = line.trim();
268
+ if (!trimmed.startsWith("data:"))
269
+ return;
270
+ const data = trimmed.slice(5).trim();
271
+ if (!data)
272
+ return;
273
+ if (data === "[DONE]") {
274
+ ended = true;
275
+ return;
276
+ }
277
+ const chunk = JSON.parse(data);
278
+ if (chunk.error) {
279
+ const detail = typeof chunk.error === "string" ? chunk.error : JSON.stringify(chunk.error);
280
+ throw new Error(`OpenAI-compatible stream error: ${this.config.apiKey ? detail.split(this.config.apiKey).join("[redacted]").slice(0, 600) : detail.slice(0, 600)}`);
281
+ }
282
+ const delta = chunk.choices?.[0]?.delta;
283
+ if (typeof chunk.choices?.[0]?.finish_reason === "string")
284
+ finishReason = chunk.choices[0].finish_reason;
285
+ if (typeof delta?.content === "string") {
286
+ content += delta.content;
287
+ if (content.length > 4000000)
288
+ throw new Error("Model stream exceeds output limit");
289
+ emitToken(delta.content);
290
+ }
291
+ if (Array.isArray(delta?.tool_calls))
292
+ for (const call of delta.tool_calls) {
293
+ const index = call.index ?? 0;
294
+ if (!Number.isInteger(index) || index < 0 || index > 128)
295
+ throw new Error("Invalid streamed tool index");
296
+ const existing = toolCallMap.get(index) ?? { id: call.id ?? `call_${index + 1}`, name: "", args: "" };
297
+ if (typeof call.id === "string")
298
+ existing.id = call.id;
299
+ if (typeof call.function?.name === "string")
300
+ existing.name = call.function.name;
301
+ if (typeof call.function?.arguments === "string")
302
+ existing.args += call.function.arguments;
303
+ if (existing.args.length > 2000000)
304
+ throw new Error("Tool arguments exceed stream limit");
305
+ toolCallMap.set(index, existing);
295
306
  }
296
- catch {
297
- // ignore malformed chunks
307
+ if (chunk.usage)
308
+ usage = chunk.usage;
309
+ };
310
+ try {
311
+ while (true) {
312
+ const { value, done } = await reader.read();
313
+ if (done) {
314
+ buffer += decoder.decode();
315
+ if (buffer.trim())
316
+ consume(buffer);
317
+ break;
298
318
  }
319
+ buffer += decoder.decode(value, { stream: true });
320
+ if (buffer.length > 2000000)
321
+ throw new Error("Model stream frame exceeds limit");
322
+ const lines = buffer.split("\n");
323
+ buffer = lines.pop() ?? "";
324
+ for (const line of lines)
325
+ consume(line);
299
326
  }
300
327
  }
328
+ finally {
329
+ await reader.cancel().catch(() => undefined);
330
+ reader.releaseLock();
331
+ }
332
+ if ((!ended && !finishReason) || finishReason === "length")
333
+ throw new Error("Model stream ended before a complete response");
334
+ if (!content && !toolCallMap.size)
335
+ throw new Error("Model stream returned no content or tool calls");
336
+ for (const call of toolCallMap.values()) {
337
+ if (!call.name || !call.id)
338
+ throw new Error("Incomplete streamed tool call");
339
+ JSON.parse(call.args);
340
+ }
301
341
  const toolCalls = toolCallMap.size > 0
302
342
  ? Array.from(toolCallMap.entries()).map(([index, call]) => ({
303
343
  id: call.id ?? `call_${index + 1}`,
@@ -360,6 +400,7 @@ export class OpenAiCompatibleProvider {
360
400
  };
361
401
  }
362
402
  finally {
403
+ request.signal?.removeEventListener("abort", onAbort);
363
404
  clearTimeout(timeout);
364
405
  }
365
406
  }
@@ -52,6 +52,7 @@ export interface ProviderUsage {
52
52
  totalTokens?: number;
53
53
  }
54
54
  export interface ProviderRequest {
55
+ signal?: AbortSignal;
55
56
  messages: ProviderMessage[];
56
57
  tools?: ProviderToolDefinition[];
57
58
  toolChoice?: "auto" | "none" | {
@@ -70,8 +71,19 @@ export interface ProviderResponse {
70
71
  toolCalls?: ProviderToolCall[];
71
72
  usage?: ProviderUsage;
72
73
  raw?: unknown;
74
+ /** Selected configured route; host labels are configuration metadata, not hardware attestation. */
75
+ route?: {
76
+ id: string;
77
+ binding: string;
78
+ configHash: string;
79
+ modelId: string;
80
+ catalogId: string;
81
+ backend?: string;
82
+ host?: string;
83
+ };
73
84
  }
74
85
  export interface ProviderConfig {
86
+ retryTransient?: boolean;
75
87
  model: string;
76
88
  /** Agent this provider serves, so failures can name it. */
77
89
  agentSlug?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderTypes.d.ts","sourceRoot":"","sources":["../../src/providers/ProviderTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ProviderTypes.d.ts","sourceRoot":"","sources":["../../src/providers/ProviderTypes.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type CodaliRuntimeAgentInput, type CodaliRuntimeEvent, type CodaliRuntimeInput, type CodaliRuntimePolicy, type CodaliRuntimeProviderInput, type CodaliRuntimeResult, type CodaliRuntimeTelemetry, type CodaliRuntimeToolManifest } from "./CodaliRuntime.js";
2
2
  import type { ProviderMessage, ProviderUsage } from "../providers/ProviderTypes.js";
3
+ import { type ScopedModelUsage } from "../providers/ConfiguredModelScope.js";
3
4
  export type CodaliJobStageKind = "router" | "planner" | "worker" | "adjudicator" | "synthesizer" | "verifier" | "repair" | (string & {});
4
5
  export type CodaliJobStageStatus = "completed" | "failed" | "skipped";
5
6
  export type CodaliJobStatus = "succeeded" | "failed" | "partial" | "needs_clarification";
@@ -178,6 +179,11 @@ export interface CodaliJobTelemetry {
178
179
  stages: CodaliJobTelemetryStage[];
179
180
  }
180
181
  export interface CodaliJobRuntimeResult {
182
+ orchestration?: {
183
+ configHash: string;
184
+ usage: ScopedModelUsage;
185
+ toolCalls: number;
186
+ };
181
187
  output: string;
182
188
  status: CodaliJobStatus;
183
189
  runId: string;