@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.1

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 (211) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +12 -8
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/CLI_SHELL.md +163 -53
  6. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  7. package/docs/RELEASES.md +16 -7
  8. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  9. package/docs/TEST_TIERS.md +26 -0
  10. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  11. package/package.json +45 -12
  12. package/packages/agentsam-repository/README.md +15 -0
  13. package/packages/agentsam-repository/package.json +25 -0
  14. package/packages/agentsam-repository/src/contracts.js +113 -0
  15. package/packages/agentsam-repository/src/index.js +3 -0
  16. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  17. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  18. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  19. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  20. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  21. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  22. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  23. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  24. package/packages/connectors/cloudflare/package.json +10 -0
  25. package/packages/connectors/cloudflare/src/index.js +127 -0
  26. package/packages/connectors/cloudflare/src/owner.js +76 -0
  27. package/packages/connectors/cloudflare/src/routes.js +223 -0
  28. package/packages/connectors/cloudflare/src/vault.js +80 -0
  29. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  30. package/packages/identity/package.json +2 -2
  31. package/packages/identity/src/contracts/auth-config.js +18 -7
  32. package/packages/identity/tests/auth-config.test.mjs +9 -5
  33. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  34. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  35. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  36. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  37. package/protocol/README.md +1 -0
  38. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  39. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  40. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  41. package/protocol/capabilities/manifest.json +47 -0
  42. package/protocol/context/context-budget.schema.json +10 -15
  43. package/protocol/context/context-item.schema.json +4 -5
  44. package/protocol/context/resolved-context-pack.schema.json +19 -14
  45. package/protocol/models/README.md +373 -0
  46. package/protocol/models/model-inventory-v2.schema.json +212 -0
  47. package/protocol/repository/repository-contract.schema.json +24 -0
  48. package/protocol/repository/repository-dependency.schema.json +24 -0
  49. package/protocol/repository/repository-identity.schema.json +17 -0
  50. package/protocol/rpc/v1/common.proto +16 -0
  51. package/protocol/rpc/v1/errors.proto +35 -0
  52. package/protocol/rpc/v1/knowledge.proto +77 -0
  53. package/services/knowledge/package-lock.json +333 -0
  54. package/services/knowledge/package.json +5 -1
  55. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  56. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  57. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  58. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  59. package/skills/catalog.json +18 -0
  60. package/src/agent/capability-adapter.js +25 -13
  61. package/src/agent/index.js +1 -0
  62. package/src/agent/responses-runner.js +353 -0
  63. package/src/capabilities/repository-snapshot.js +3 -3
  64. package/src/cli.js +118 -31
  65. package/src/cloudflare/cpu-profile.js +115 -0
  66. package/src/cloudflare/index.js +14 -0
  67. package/src/cloudflare/wrangler.js +132 -0
  68. package/src/commands/account-auth.js +47 -0
  69. package/src/commands/cloudflare.js +58 -0
  70. package/src/commands/connections.js +93 -0
  71. package/src/commands/context-economics.js +129 -0
  72. package/src/commands/context.js +1 -1
  73. package/src/commands/db.js +20 -3
  74. package/src/commands/deploy.js +39 -3
  75. package/src/commands/env.js +90 -0
  76. package/src/commands/eval.js +63 -0
  77. package/src/commands/interactive.js +2 -5
  78. package/src/commands/knowledge.js +12 -4
  79. package/src/commands/merkle-persist.js +30 -11
  80. package/src/commands/merkle.js +1 -1
  81. package/src/commands/models.js +149 -46
  82. package/src/commands/ollama.js +26 -0
  83. package/src/commands/preferences.js +130 -61
  84. package/src/commands/resume.js +67 -0
  85. package/src/commands/security.js +5 -3
  86. package/src/commands/shell.js +568 -119
  87. package/src/commands/tunnel.js +2 -2
  88. package/src/commands/whoami.js +86 -0
  89. package/src/context/budget.js +68 -6
  90. package/src/context/index.js +3 -1
  91. package/src/context/rehydrate.js +35 -0
  92. package/src/context/resolve.js +44 -12
  93. package/src/errors/contract.js +236 -0
  94. package/src/errors/diagnostic.js +160 -0
  95. package/src/errors/index.js +23 -0
  96. package/src/eval/context.js +191 -0
  97. package/src/eval/index.js +1 -0
  98. package/src/index.js +68 -2
  99. package/src/knowledge/service/auth.js +13 -0
  100. package/src/knowledge/service/grpc-client.js +115 -0
  101. package/src/knowledge/service/grpc-codec.js +237 -0
  102. package/src/knowledge/service/grpc-server.js +83 -0
  103. package/src/knowledge/service/job-engine.js +248 -0
  104. package/src/knowledge/service/server.js +87 -135
  105. package/src/knowledge/source.js +1 -1
  106. package/src/lib/account-session.js +98 -0
  107. package/src/lib/agent-instructions.js +73 -0
  108. package/src/lib/auth.js +4 -0
  109. package/src/lib/cli-preferences.js +55 -24
  110. package/src/lib/deploy/git-guard.js +69 -0
  111. package/src/lib/deploy/health.js +57 -0
  112. package/src/lib/deploy/local-studio.js +283 -0
  113. package/src/lib/deploy/secret-scan.js +65 -0
  114. package/src/lib/deploy-receipt/index.js +2 -2
  115. package/src/lib/detect-context.js +2 -2
  116. package/src/lib/execution-approvals.js +59 -0
  117. package/src/lib/knowledge-docker.js +6 -3
  118. package/src/lib/local-sessions.js +148 -0
  119. package/src/lib/local-status.js +1 -1
  120. package/src/lib/project-config.js +1 -1
  121. package/src/lib/provider-credentials.js +183 -0
  122. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  123. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  124. package/src/lib/slash-commands.js +23 -16
  125. package/src/local/migrations.js +93 -0
  126. package/src/local/runtime-store.js +141 -0
  127. package/src/local/sqlite.js +2 -0
  128. package/src/local-pty/server.js +113 -51
  129. package/src/models/catalog.js +135 -0
  130. package/src/models/discovery.js +292 -0
  131. package/src/models/index.js +7 -0
  132. package/src/providers/anthropic-messages.js +192 -0
  133. package/src/providers/cloudflare-chat.js +183 -0
  134. package/src/providers/factory.js +69 -0
  135. package/src/providers/gemini-generate-content.js +208 -0
  136. package/src/providers/index.js +10 -0
  137. package/src/providers/ollama-chat.js +148 -0
  138. package/src/providers/openai-responses.js +426 -0
  139. package/src/repository/index.js +14 -2
  140. package/src/rpc/generated/common_grpc_pb.js +1 -0
  141. package/src/rpc/generated/common_pb.js +536 -0
  142. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  143. package/src/rpc/generated/errors_pb.js +482 -0
  144. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  145. package/src/rpc/generated/knowledge_pb.js +2168 -0
  146. package/src/rpc/generated/package.json +3 -0
  147. package/src/security/process.js +35 -9
  148. package/src/security/trust-boundary.js +2 -2
  149. package/src/telemetry/contracts.js +203 -0
  150. package/src/telemetry/events.js +51 -0
  151. package/src/telemetry/index.js +8 -0
  152. package/src/tools/hydrate.js +35 -0
  153. package/src/tools/index.js +1 -0
  154. package/src/ui/boot.js +15 -17
  155. package/src/ui/cli/activity.js +76 -0
  156. package/src/ui/cli/compaction.js +15 -0
  157. package/src/ui/cli/footer.js +39 -0
  158. package/src/ui/cli/help.js +192 -0
  159. package/src/ui/cli/plan.js +20 -0
  160. package/src/ui/cli/runtime-events.js +110 -0
  161. package/src/ui/cli/waiting.js +16 -0
  162. package/src/ui/merkle/render.js +1 -1
  163. package/test/account-session.test.mjs +36 -0
  164. package/test/cli/preferences-runtime.test.mjs +11 -0
  165. package/test/cli/runtime-ui.test.mjs +74 -0
  166. package/test/cli-preferences.test.mjs +26 -5
  167. package/test/cloudflare-connector.test.mjs +96 -0
  168. package/test/cloudflare-runtime.test.mjs +75 -0
  169. package/test/context.test.mjs +61 -12
  170. package/test/deploy-health-scan.test.mjs +67 -0
  171. package/test/error-diagnostics.test.mjs +115 -0
  172. package/test/eval-context.test.mjs +37 -0
  173. package/test/execution-approvals.test.mjs +27 -0
  174. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  175. package/test/integration/cli-help.test.mjs +37 -0
  176. package/test/integration/knowledge-rpc.test.mjs +112 -0
  177. package/test/integration/merkle-cli.test.mjs +61 -0
  178. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  179. package/test/integration/provider-env-cli.test.mjs +49 -0
  180. package/test/integration/provider-factory.test.mjs +197 -0
  181. package/test/integration/repository-company-graph.test.mjs +90 -0
  182. package/test/integration/runtime-migrations.test.mjs +82 -0
  183. package/test/knowledge-service.test.mjs +5 -0
  184. package/test/knowledge.test.mjs +16 -0
  185. package/test/live/terminal-transport.live.test.mjs +24 -0
  186. package/test/local-sessions.test.mjs +48 -0
  187. package/test/local-studio-deploy.test.mjs +83 -0
  188. package/test/model-catalog.test.mjs +43 -0
  189. package/test/models.test.mjs +127 -16
  190. package/test/npm10-lock.test.mjs +29 -0
  191. package/test/ollama.test.mjs +21 -0
  192. package/test/openai-responses.test.mjs +95 -0
  193. package/test/portable-context.test.mjs +1 -1
  194. package/test/provider-credentials.test.mjs +96 -0
  195. package/test/rehydrate.test.mjs +25 -0
  196. package/test/release-hygiene.test.mjs +13 -5
  197. package/test/responses-runner.test.mjs +150 -0
  198. package/test/shell.test.mjs +92 -23
  199. package/test/smoke.mjs +4 -1
  200. package/test/telemetry.test.mjs +79 -0
  201. package/test/terminal/local-pty.mock.test.mjs +151 -0
  202. package/test/tools-search.test.mjs +14 -1
  203. package/test/whoami-resume.test.mjs +56 -0
  204. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  205. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  206. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  207. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  208. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  209. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  210. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  211. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
@@ -0,0 +1,292 @@
1
+ import { getModelRecord } from './catalog.js';
2
+
3
+ function clean(value) { return value == null ? '' : String(value).trim(); }
4
+ function positiveInt(value) {
5
+ const n = Number(value);
6
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
7
+ }
8
+ function timeoutSignal(ms = 8_000) {
9
+ return typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(ms) : undefined;
10
+ }
11
+ function failure(error, attempted = true) {
12
+ return { attempted, ok: false, models: [], error: error?.message || String(error || 'unknown error') };
13
+ }
14
+
15
+ function providerReference(provider, id) {
16
+ if (provider !== 'anthropic') return null;
17
+ const million = new Set([
18
+ 'claude-fable-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7',
19
+ 'claude-opus-4-6', 'claude-sonnet-5', 'claude-sonnet-4-6',
20
+ ]);
21
+ const twoHundredK = new Set([
22
+ 'claude-opus-4-5-20251101', 'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001',
23
+ ]);
24
+ const contextWindow = million.has(id) ? 1_000_000 : twoHundredK.has(id) ? 200_000 : null;
25
+ if (!contextWindow) return null;
26
+ return Object.freeze({
27
+ model_key: `anthropic:${id}`,
28
+ provider: 'anthropic',
29
+ provider_model_id: id,
30
+ label: id,
31
+ context_window: contextWindow,
32
+ max_output_tokens: ['claude-opus-5', 'claude-sonnet-5'].includes(id) ? 128_000 : null,
33
+ reasoning_efforts: Object.freeze(['auto']),
34
+ service_tiers: Object.freeze(['default']),
35
+ capabilities: Object.freeze({ messages: true, function_calling: true, prompt_caching: true, compaction: true }),
36
+ source: Object.freeze({
37
+ url: 'https://docs.anthropic.com/en/docs/about-claude/models/overview',
38
+ as_of: '2026-09-17',
39
+ }),
40
+ });
41
+ }
42
+
43
+ function fallbackRecord(provider, id) {
44
+ const record = getModelRecord(`${provider}:${id}`) || getModelRecord(id) || providerReference(provider, id);
45
+ return record || null;
46
+ }
47
+
48
+ function baseRecord(provider, id, values = {}) {
49
+ const fallback = fallbackRecord(provider, id);
50
+ const contextWindow = positiveInt(values.context_window ?? values.contextWindow ?? fallback?.context_window);
51
+ const maxOutput = positiveInt(values.max_output_tokens ?? values.maxOutputTokens ?? fallback?.max_output_tokens);
52
+ return Object.freeze({
53
+ model_key: `${provider}:${id}`,
54
+ provider,
55
+ provider_model_id: id,
56
+ label: clean(values.label) || fallback?.label || id,
57
+ availability: 'available',
58
+ availability_source: 'provider_api',
59
+ context_window: contextWindow,
60
+ context_window_source: values.context_window != null || values.contextWindow != null
61
+ ? 'provider_api'
62
+ : fallback?.context_window ? 'sdk_reference' : 'unknown',
63
+ max_output_tokens: maxOutput,
64
+ max_output_tokens_source: values.max_output_tokens != null || values.maxOutputTokens != null
65
+ ? 'provider_api'
66
+ : fallback?.max_output_tokens ? 'sdk_reference' : 'unknown',
67
+ reasoning_efforts: Object.freeze(
68
+ Array.isArray(values.reasoning_efforts) && values.reasoning_efforts.length
69
+ ? [...values.reasoning_efforts]
70
+ : fallback?.reasoning_efforts ? [...fallback.reasoning_efforts] : ['auto'],
71
+ ),
72
+ service_tiers: Object.freeze(
73
+ Array.isArray(values.service_tiers) && values.service_tiers.length
74
+ ? [...values.service_tiers]
75
+ : fallback?.service_tiers ? [...fallback.service_tiers] : ['default'],
76
+ ),
77
+ capabilities: Object.freeze({
78
+ ...(fallback?.capabilities || {}),
79
+ ...(values.capabilities || {}),
80
+ }),
81
+ pricing: values.pricing || fallback?.pricing || null,
82
+ context_policy: values.context_policy || fallback?.context_policy || null,
83
+ batch: values.batch || fallback?.batch || null,
84
+ source: Object.freeze({
85
+ kind: 'provider_api',
86
+ url: clean(values.source_url) || null,
87
+ discovered_at: new Date().toISOString(),
88
+ fallback: fallback?.source || null,
89
+ }),
90
+ metadata: Object.freeze(values.metadata && typeof values.metadata === 'object' ? { ...values.metadata } : {}),
91
+ });
92
+ }
93
+
94
+ async function fetchJson(fetchImpl, url, init) {
95
+ const response = await fetchImpl(url, { ...init, signal: init?.signal || timeoutSignal() });
96
+ let body = null;
97
+ try { body = await response.json(); } catch { body = null; }
98
+ if (!response.ok) {
99
+ const message = clean(body?.error?.message || body?.message || body?.errors?.[0]?.message) || `HTTP ${response.status}`;
100
+ const error = new Error(message);
101
+ error.status = response.status;
102
+ throw error;
103
+ }
104
+ return body;
105
+ }
106
+
107
+ export async function discoverOpenAIModels(apiKey, fetchImpl = fetch) {
108
+ if (!clean(apiKey)) return failure('credential unavailable', false);
109
+ try {
110
+ const body = await fetchJson(fetchImpl, 'https://api.openai.com/v1/models', {
111
+ headers: { authorization: `Bearer ${clean(apiKey)}` },
112
+ });
113
+ const models = (Array.isArray(body?.data) ? body.data : [])
114
+ .map((row) => clean(row?.id))
115
+ .filter(Boolean)
116
+ .map((id) => baseRecord('openai', id, {
117
+ source_url: 'https://api.openai.com/v1/models',
118
+ capabilities: { responses: true },
119
+ }));
120
+ return { attempted: true, ok: true, models, error: null };
121
+ } catch (error) { return failure(error); }
122
+ }
123
+
124
+ export async function discoverAnthropicModels(apiKey, fetchImpl = fetch) {
125
+ if (!clean(apiKey)) return failure('credential unavailable', false);
126
+ try {
127
+ const body = await fetchJson(fetchImpl, 'https://api.anthropic.com/v1/models?limit=1000', {
128
+ headers: {
129
+ 'x-api-key': clean(apiKey),
130
+ 'anthropic-version': '2023-06-01',
131
+ },
132
+ });
133
+ const models = (Array.isArray(body?.data) ? body.data : [])
134
+ .map((row) => {
135
+ const id = clean(row?.id);
136
+ if (!id) return null;
137
+ return baseRecord('anthropic', id, {
138
+ label: clean(row?.display_name) || id,
139
+ source_url: 'https://api.anthropic.com/v1/models',
140
+ capabilities: { messages: true, function_calling: true, prompt_caching: true },
141
+ metadata: { created_at: row?.created_at || null, type: row?.type || null },
142
+ });
143
+ })
144
+ .filter(Boolean);
145
+ return { attempted: true, ok: true, models, error: null };
146
+ } catch (error) { return failure(error); }
147
+ }
148
+
149
+ export async function discoverGeminiModels(apiKey, fetchImpl = fetch) {
150
+ if (!clean(apiKey)) return failure('credential unavailable', false);
151
+ try {
152
+ const body = await fetchJson(
153
+ fetchImpl,
154
+ `https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000&key=${encodeURIComponent(clean(apiKey))}`,
155
+ {},
156
+ );
157
+ const models = (Array.isArray(body?.models) ? body.models : [])
158
+ .filter((row) => (row?.supportedGenerationMethods || []).includes('generateContent'))
159
+ .map((row) => {
160
+ const id = clean(row?.baseModelId || row?.name).replace(/^models\//, '');
161
+ if (!id) return null;
162
+ return baseRecord('gemini', id, {
163
+ label: clean(row?.displayName) || id,
164
+ context_window: positiveInt(row?.inputTokenLimit),
165
+ max_output_tokens: positiveInt(row?.outputTokenLimit),
166
+ reasoning_efforts: row?.thinking === true ? ['auto', 'low', 'medium', 'high'] : ['auto'],
167
+ source_url: 'https://generativelanguage.googleapis.com/v1beta/models',
168
+ capabilities: {
169
+ generate_content: true,
170
+ function_calling: true,
171
+ thinking: row?.thinking === true,
172
+ },
173
+ metadata: {
174
+ version: row?.version || null,
175
+ supported_generation_methods: row?.supportedGenerationMethods || [],
176
+ },
177
+ });
178
+ })
179
+ .filter(Boolean);
180
+ return { attempted: true, ok: true, models, error: null };
181
+ } catch (error) { return failure(error); }
182
+ }
183
+
184
+ function xaiPricing(row) {
185
+ const input = Number(row?.prompt_text_token_price);
186
+ const cached = Number(row?.cached_prompt_text_token_price);
187
+ const output = Number(row?.completion_text_token_price);
188
+ if (![input, output].every(Number.isFinite)) return null;
189
+ // xAI model API prices are returned in nanos per token. Convert to USD / million tokens.
190
+ const nanosToPerMillionUsd = (nanos) => Number.isFinite(nanos) ? nanos / 1000 : 0;
191
+ return Object.freeze({
192
+ currency: 'USD',
193
+ unit: 'per_million_tokens',
194
+ input: nanosToPerMillionUsd(input),
195
+ cached_input: nanosToPerMillionUsd(cached),
196
+ cache_write: nanosToPerMillionUsd(input),
197
+ output: nanosToPerMillionUsd(output),
198
+ thresholds: Object.freeze(
199
+ Number.isFinite(Number(row?.long_context_threshold))
200
+ ? [Object.freeze({
201
+ input_tokens_gt: Number(row.long_context_threshold),
202
+ applies_to_full_request: true,
203
+ multipliers: Object.freeze({
204
+ input: Number(row?.prompt_text_token_price_long_context) / input || 1,
205
+ cached_input: 1,
206
+ cache_write: Number(row?.prompt_text_token_price_long_context) / input || 1,
207
+ output: Number(row?.completion_text_token_price_long_context) / output || 1,
208
+ }),
209
+ })]
210
+ : [],
211
+ ),
212
+ service_tier_multipliers: Object.freeze({ default: 1 }),
213
+ source: 'https://api.x.ai/v1/models',
214
+ as_of: new Date().toISOString().slice(0, 10),
215
+ });
216
+ }
217
+
218
+ export async function discoverXaiModels(apiKey, fetchImpl = fetch) {
219
+ if (!clean(apiKey)) return failure('credential unavailable', false);
220
+ try {
221
+ const body = await fetchJson(fetchImpl, 'https://api.x.ai/v1/models', {
222
+ headers: { authorization: `Bearer ${clean(apiKey)}` },
223
+ });
224
+ const models = (Array.isArray(body?.data) ? body.data : [])
225
+ .map((row) => {
226
+ const id = clean(row?.id);
227
+ if (!id || /image|video|voice|embedding/i.test(id)) return null;
228
+ return baseRecord('grok', id, {
229
+ context_window: positiveInt(row?.context_length),
230
+ pricing: xaiPricing(row),
231
+ source_url: 'https://api.x.ai/v1/models',
232
+ capabilities: { responses: true, function_calling: true, prompt_caching: true },
233
+ metadata: {
234
+ aliases: row?.aliases || [],
235
+ created: row?.created || null,
236
+ owned_by: row?.owned_by || null,
237
+ },
238
+ });
239
+ })
240
+ .filter(Boolean);
241
+ return { attempted: true, ok: true, models, error: null };
242
+ } catch (error) { return failure(error); }
243
+ }
244
+
245
+ function cloudflareTaskName(task) {
246
+ if (typeof task === 'string') return clean(task);
247
+ if (task && typeof task === 'object') return clean(task.name || task.id);
248
+ return '';
249
+ }
250
+
251
+ export async function discoverCloudflareModels(apiToken, accountId, fetchImpl = fetch) {
252
+ if (!clean(apiToken)) return failure('credential unavailable', false);
253
+ if (!clean(accountId)) return failure('ACCOUNT_ID is required for Workers AI discovery');
254
+ try {
255
+ const url = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(clean(accountId))}/ai/models/search`;
256
+ const body = await fetchJson(fetchImpl, url, {
257
+ headers: { authorization: `Bearer ${clean(apiToken)}` },
258
+ });
259
+ if (body?.success === false) throw new Error(clean(body?.errors?.[0]?.message) || 'Cloudflare API error');
260
+ const models = (Array.isArray(body?.result) ? body.result : [])
261
+ .map((row) => {
262
+ const id = clean(row?.name);
263
+ const task = cloudflareTaskName(row?.task);
264
+ if (!id || task.toLowerCase() !== 'text generation') return null;
265
+ return baseRecord('cloudflare', id, {
266
+ label: id,
267
+ source_url: url,
268
+ capabilities: { workers_ai: true },
269
+ metadata: {
270
+ task,
271
+ author: clean(row?.author) || null,
272
+ description: clean(row?.description) || null,
273
+ },
274
+ });
275
+ })
276
+ .filter(Boolean);
277
+ return { attempted: true, ok: true, models, error: null, account_id: clean(accountId) };
278
+ } catch (error) { return failure(error); }
279
+ }
280
+
281
+ export async function discoverProviderModels(provider, credential, options = {}) {
282
+ const fetchImpl = options.fetchImpl || fetch;
283
+ switch (clean(provider).toLowerCase()) {
284
+ case 'openai': return discoverOpenAIModels(credential?.value, fetchImpl);
285
+ case 'anthropic': return discoverAnthropicModels(credential?.value, fetchImpl);
286
+ case 'gemini': return discoverGeminiModels(credential?.value, fetchImpl);
287
+ case 'grok':
288
+ case 'xai': return discoverXaiModels(credential?.value, fetchImpl);
289
+ case 'cloudflare': return discoverCloudflareModels(credential?.value, credential?.account_id, fetchImpl);
290
+ default: return failure(`unsupported provider: ${provider}`, false);
291
+ }
292
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ MODEL_CATALOG_SCHEMA,
3
+ MODEL_CATALOG,
4
+ listModelCatalog,
5
+ getModelRecord,
6
+ calculateModelCost,
7
+ } from './catalog.js';
@@ -0,0 +1,192 @@
1
+ import { createAgentEvent, createUsageSnapshot } from '../telemetry/index.js';
2
+ import { calculateModelCost } from '../models/index.js';
3
+ import { diagnosticFromError } from '../errors/index.js';
4
+
5
+ function clean(value) { return value == null ? '' : String(value).trim(); }
6
+ function integer(value) { const n = Number(value ?? 0); return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; }
7
+ function emit(emitFn, type, payload, runId) {
8
+ if (typeof emitFn === 'function') emitFn(createAgentEvent(type, payload, { runId }));
9
+ }
10
+ function signal(ms) {
11
+ return typeof AbortSignal?.timeout === 'function' && Number(ms) > 0 ? AbortSignal.timeout(Number(ms)) : undefined;
12
+ }
13
+ function toolsForAnthropic(tools = []) {
14
+ return tools.map((tool) => ({
15
+ name: tool.name,
16
+ description: tool.description || '',
17
+ input_schema: tool.parameters || { type: 'object', properties: {} },
18
+ }));
19
+ }
20
+ function textFromContent(content = []) {
21
+ return content.filter((part) => part?.type === 'text').map((part) => String(part.text || '')).join('');
22
+ }
23
+ function callsFromContent(content = []) {
24
+ return content.filter((part) => part?.type === 'tool_use').map((part) => ({
25
+ call_id: part.id,
26
+ name: part.name,
27
+ arguments: JSON.stringify(part.input || {}),
28
+ }));
29
+ }
30
+ function usageParts(response = {}) {
31
+ const usage = response.usage || {};
32
+ return {
33
+ input_tokens: integer(usage.input_tokens),
34
+ cached_input_tokens: integer(usage.cache_read_input_tokens),
35
+ cache_write_tokens: integer(usage.cache_creation_input_tokens),
36
+ output_tokens: integer(usage.output_tokens),
37
+ reasoning_tokens: 0,
38
+ };
39
+ }
40
+ function cumulative(base, delta) {
41
+ return {
42
+ input_tokens: integer(base?.input_tokens) + delta.input_tokens,
43
+ output_tokens: integer(base?.output_tokens) + delta.output_tokens,
44
+ cached_input_tokens: integer(base?.cached_input_tokens) + delta.cached_input_tokens,
45
+ cache_write_tokens: integer(base?.cache_write_tokens) + delta.cache_write_tokens,
46
+ reasoning_tokens: integer(base?.reasoning_tokens),
47
+ };
48
+ }
49
+
50
+ export function createAnthropicMessagesAdapter(options = {}) {
51
+ const apiKey = clean(options.apiKey || process.env.ANTHROPIC_API_KEY);
52
+ const fetchImpl = options.fetchImpl || fetch;
53
+ const baseUrl = clean(options.baseUrl || 'https://api.anthropic.com/v1').replace(/\/$/, '');
54
+
55
+ async function request(body, params = {}) {
56
+ if (!apiKey) throw new Error('anthropic_api_key_required');
57
+ const response = await fetchImpl(baseUrl + '/messages', {
58
+ method: 'POST',
59
+ headers: {
60
+ 'x-api-key': apiKey,
61
+ 'anthropic-version': '2023-06-01',
62
+ 'content-type': 'application/json',
63
+ },
64
+ body: JSON.stringify(body),
65
+ signal: signal(params.timeoutMs || options.timeoutMs),
66
+ });
67
+ const data = await response.json().catch(() => ({}));
68
+ if (!response.ok) {
69
+ const error = new Error(data?.error?.message || data?.message || `Anthropic HTTP ${response.status}`);
70
+ error.status = response.status;
71
+ error.provider = 'anthropic';
72
+ throw error;
73
+ }
74
+ return data;
75
+ }
76
+
77
+ async function send(messages, params = {}) {
78
+ const record = params.modelRecord || options.modelRecord;
79
+ if (!record || record.provider !== 'anthropic') throw new Error('anthropic_model_record_required');
80
+ const emitFn = params.emit || options.emit;
81
+ const runId = params.runId;
82
+ const model = record.provider_model_id;
83
+ const body = {
84
+ model,
85
+ max_tokens: Number(params.maxOutputTokens) > 0 ? Number(params.maxOutputTokens) : 8192,
86
+ messages,
87
+ ...(clean(params.instructions) ? {
88
+ system: [{
89
+ type: 'text',
90
+ text: String(params.instructions),
91
+ cache_control: { type: 'ephemeral' },
92
+ }],
93
+ } : {}),
94
+ ...(params.tools?.length ? { tools: toolsForAnthropic(params.tools) } : {}),
95
+ };
96
+
97
+ emit(emitFn, 'model.started', {
98
+ provider: 'anthropic',
99
+ model,
100
+ reasoning_effort: params.reasoningEffort || 'auto',
101
+ requested_service_tier: 'default',
102
+ }, runId);
103
+
104
+ let response;
105
+ try {
106
+ response = await request(body, params);
107
+ } catch (error) {
108
+ const diagnostic = diagnosticFromError(error, { source: 'anthropic', kind: 'provider_error' });
109
+ emit(emitFn, 'error.observed', diagnostic, runId);
110
+ emit(emitFn, 'run.failed', { stage: 'model', provider: 'anthropic', model, error: diagnostic }, runId);
111
+ throw error;
112
+ }
113
+
114
+ const delta = usageParts(response);
115
+ const usageSnapshot = createUsageSnapshot({
116
+ current_context: { input_tokens: delta.input_tokens, window_tokens: Number(record.context_window) || 0 },
117
+ cumulative: cumulative(params.cumulativeUsage, delta),
118
+ estimate_kind: 'provider',
119
+ });
120
+ const cost = record.pricing ? calculateModelCost(record, delta, { serviceTier: 'default' }) : null;
121
+ if (cost) emit(emitFn, 'cost.snapshot', cost, runId);
122
+ emit(emitFn, 'usage.snapshot', usageSnapshot, runId);
123
+ emit(emitFn, 'model.completed', {
124
+ provider: 'anthropic',
125
+ model,
126
+ response_id: response.id || null,
127
+ status: response.stop_reason || 'completed',
128
+ requested_service_tier: 'default',
129
+ actual_service_tier: 'default',
130
+ }, runId);
131
+
132
+ const assistant = { role: 'assistant', content: response.content || [] };
133
+ const nextMessages = [...messages, assistant];
134
+ return Object.freeze({
135
+ provider: 'anthropic',
136
+ model,
137
+ response_id: response.id || null,
138
+ status: response.stop_reason || 'completed',
139
+ output_text: textFromContent(response.content),
140
+ tool_calls: Object.freeze(callsFromContent(response.content)),
141
+ usage_delta: Object.freeze(delta),
142
+ usage_snapshot: usageSnapshot,
143
+ cost,
144
+ provider_state: Object.freeze({ messages: nextMessages }),
145
+ requested_service_tier: 'default',
146
+ actual_service_tier: 'default',
147
+ raw: response,
148
+ });
149
+ }
150
+
151
+ async function create(params = {}) {
152
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
153
+ const input = typeof params.input === 'string' ? params.input : JSON.stringify(params.input ?? '');
154
+ const messages = [...prior, { role: 'user', content: [{ type: 'text', text: input }] }];
155
+ return send(messages, params);
156
+ }
157
+
158
+ async function continueWithToolOutputs(params = {}) {
159
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
160
+ if (!prior.length) throw new Error('anthropic_provider_state_required');
161
+ const content = (params.toolOutputs || []).map((row) => ({
162
+ type: 'tool_result',
163
+ tool_use_id: row.call_id || row.callId,
164
+ content: typeof row.output === 'string' ? row.output : JSON.stringify(row.output ?? null),
165
+ }));
166
+ if (!content.length) throw new Error('anthropic_tool_outputs_required');
167
+ return send([...prior, { role: 'user', content }], params);
168
+ }
169
+
170
+ async function compact(params = {}) {
171
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
172
+ if (!prior.length) throw new Error('anthropic_provider_state_required');
173
+ const emitFn = params.emit || options.emit;
174
+ const startedAt = Date.now();
175
+ emit(emitFn, 'context.compaction.started', { provider: 'anthropic', model: params.model }, params.runId);
176
+ const summary = await send([...prior, {
177
+ role: 'user',
178
+ content: [{ type: 'text', text: 'Create a compact continuation summary of this conversation. Preserve the objective, decisions, changed files, active plan, unresolved failures, tool results that still matter, and constraints. Do not add new work.' }],
179
+ }], { ...params, tools: [], maxOutputTokens: Math.min(Number(params.maxOutputTokens) || 4096, 4096) });
180
+ const summaryText = String(summary.output_text || '').trim();
181
+ const compactState = Object.freeze({ messages: [{ role: 'user', content: [{ type: 'text', text: `Prior conversation continuation summary:\n${summaryText}` }] }] });
182
+ emit(emitFn, 'context.compaction.completed', {
183
+ provider: 'anthropic', model: params.model, summary_text: summaryText,
184
+ tokens_before: params.tokensBefore ?? null,
185
+ tokens_after: Math.ceil(summaryText.length / 4),
186
+ duration_ms: Date.now() - startedAt,
187
+ }, params.runId);
188
+ return Object.freeze({ provider: 'anthropic', output: Object.freeze([]), provider_state: compactState, summary_text: summaryText, usage: summary.usage_delta || null });
189
+ }
190
+
191
+ return Object.freeze({ provider: 'anthropic', create, continueWithToolOutputs, compact });
192
+ }
@@ -0,0 +1,183 @@
1
+ import { createAgentEvent, createUsageSnapshot } from '../telemetry/index.js';
2
+ import { diagnosticFromError } from '../errors/index.js';
3
+
4
+ function clean(value) { return value == null ? '' : String(value).trim(); }
5
+ function integer(value) { const n = Number(value ?? 0); return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; }
6
+ function emit(emitFn, type, payload, runId) {
7
+ if (typeof emitFn === 'function') emitFn(createAgentEvent(type, payload, { runId }));
8
+ }
9
+ function toolsForChat(tools = []) {
10
+ return tools.map((tool) => ({
11
+ type: 'function',
12
+ function: {
13
+ name: tool.name,
14
+ description: tool.description || '',
15
+ parameters: tool.parameters || { type: 'object', properties: {} },
16
+ },
17
+ }));
18
+ }
19
+ function usageParts(response = {}) {
20
+ const usage = response.usage || {};
21
+ return {
22
+ input_tokens: integer(usage.prompt_tokens),
23
+ cached_input_tokens: integer(usage.prompt_tokens_details?.cached_tokens),
24
+ cache_write_tokens: 0,
25
+ output_tokens: integer(usage.completion_tokens),
26
+ reasoning_tokens: integer(usage.completion_tokens_details?.reasoning_tokens),
27
+ };
28
+ }
29
+ function cumulative(base, delta) {
30
+ return {
31
+ input_tokens: integer(base?.input_tokens) + delta.input_tokens,
32
+ output_tokens: integer(base?.output_tokens) + delta.output_tokens,
33
+ cached_input_tokens: integer(base?.cached_input_tokens) + delta.cached_input_tokens,
34
+ cache_write_tokens: integer(base?.cache_write_tokens),
35
+ reasoning_tokens: integer(base?.reasoning_tokens) + delta.reasoning_tokens,
36
+ };
37
+ }
38
+ function toolCalls(message = {}) {
39
+ return (message.tool_calls || []).map((call) => ({
40
+ call_id: call.id,
41
+ name: call.function?.name,
42
+ arguments: typeof call.function?.arguments === 'string'
43
+ ? call.function.arguments
44
+ : JSON.stringify(call.function?.arguments || {}),
45
+ }));
46
+ }
47
+
48
+ export function createCloudflareChatAdapter(options = {}) {
49
+ const credential = options.credential || {};
50
+ const apiToken = clean(credential.value || process.env.CLOUDFLARE_API_TOKEN);
51
+ const accountId = clean(credential.account_id || process.env.CLOUDFLARE_ACCOUNT_ID || process.env.ACCOUNT_ID);
52
+ const gatewayId = clean(options.gatewayId || process.env.CLOUDFLARE_AI_GATEWAY_ID || 'default');
53
+ const fetchImpl = options.fetchImpl || fetch;
54
+
55
+ async function request(body, params = {}) {
56
+ if (!apiToken) throw new Error('cloudflare_api_token_required');
57
+ if (!accountId) throw new Error('cloudflare_account_id_required');
58
+ const url = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountId)}/ai/v1/chat/completions`;
59
+ const response = await fetchImpl(url, {
60
+ method: 'POST',
61
+ headers: {
62
+ authorization: `Bearer ${apiToken}`,
63
+ 'content-type': 'application/json',
64
+ 'cf-aig-gateway-id': gatewayId,
65
+ },
66
+ body: JSON.stringify(body),
67
+ signal: typeof AbortSignal?.timeout === 'function' && Number(params.timeoutMs || options.timeoutMs) > 0
68
+ ? AbortSignal.timeout(Number(params.timeoutMs || options.timeoutMs))
69
+ : undefined,
70
+ });
71
+ const data = await response.json().catch(() => ({}));
72
+ if (!response.ok) {
73
+ const error = new Error(data?.error?.message || data?.errors?.[0]?.message || data?.message || `Cloudflare Workers AI HTTP ${response.status}`);
74
+ error.status = response.status;
75
+ error.provider = 'cloudflare';
76
+ throw error;
77
+ }
78
+ return data;
79
+ }
80
+
81
+ async function send(messages, params = {}) {
82
+ const record = params.modelRecord || options.modelRecord;
83
+ if (!record || record.provider !== 'cloudflare') throw new Error('cloudflare_model_record_required');
84
+ const emitFn = params.emit || options.emit;
85
+ const model = record.provider_model_id;
86
+ const tools = toolsForChat(params.tools || []);
87
+ emit(emitFn, 'model.started', { provider: 'cloudflare', model, reasoning_effort: 'auto', requested_service_tier: 'default' }, params.runId);
88
+
89
+ let response;
90
+ try {
91
+ response = await request({
92
+ model,
93
+ messages,
94
+ ...(tools.length ? { tools, tool_choice: 'auto' } : {}),
95
+ ...(Number(params.maxOutputTokens) > 0 ? { max_tokens: Number(params.maxOutputTokens) } : {}),
96
+ }, params);
97
+ } catch (error) {
98
+ const diagnostic = diagnosticFromError(error, { source: 'cloudflare', kind: 'provider_error' });
99
+ emit(emitFn, 'error.observed', diagnostic, params.runId);
100
+ emit(emitFn, 'run.failed', { stage: 'model', provider: 'cloudflare', model, error: diagnostic }, params.runId);
101
+ throw error;
102
+ }
103
+
104
+ const message = response?.choices?.[0]?.message || { role: 'assistant', content: '' };
105
+ const delta = usageParts(response);
106
+ const usageSnapshot = createUsageSnapshot({
107
+ current_context: { input_tokens: delta.input_tokens, window_tokens: Number(record.context_window) || 0 },
108
+ cumulative: cumulative(params.cumulativeUsage, delta),
109
+ estimate_kind: 'provider',
110
+ });
111
+ emit(emitFn, 'usage.snapshot', usageSnapshot, params.runId);
112
+ emit(emitFn, 'model.completed', {
113
+ provider: 'cloudflare', model, response_id: response.id || null,
114
+ status: response?.choices?.[0]?.finish_reason || 'completed',
115
+ requested_service_tier: 'default', actual_service_tier: 'default',
116
+ }, params.runId);
117
+
118
+ return Object.freeze({
119
+ provider: 'cloudflare',
120
+ model,
121
+ response_id: response.id || null,
122
+ status: response?.choices?.[0]?.finish_reason || 'completed',
123
+ output_text: typeof message.content === 'string' ? message.content : '',
124
+ tool_calls: Object.freeze(toolCalls(message)),
125
+ usage_delta: Object.freeze(delta),
126
+ usage_snapshot: usageSnapshot,
127
+ cost: null,
128
+ provider_state: Object.freeze({ messages: [...messages, message] }),
129
+ requested_service_tier: 'default',
130
+ actual_service_tier: 'default',
131
+ raw: response,
132
+ });
133
+ }
134
+
135
+ async function create(params = {}) {
136
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
137
+ const messages = [...prior];
138
+ if (clean(params.instructions) && !messages.some((row) => row.role === 'system')) {
139
+ messages.unshift({ role: 'system', content: String(params.instructions) });
140
+ }
141
+ messages.push({ role: 'user', content: typeof params.input === 'string' ? params.input : JSON.stringify(params.input ?? '') });
142
+ return send(messages, params);
143
+ }
144
+
145
+ async function continueWithToolOutputs(params = {}) {
146
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
147
+ if (!prior.length) throw new Error('cloudflare_provider_state_required');
148
+ const toolMessages = (params.toolOutputs || []).map((row) => ({
149
+ role: 'tool',
150
+ tool_call_id: row.call_id || row.callId,
151
+ content: typeof row.output === 'string' ? row.output : JSON.stringify(row.output ?? null),
152
+ }));
153
+ if (!toolMessages.length) throw new Error('cloudflare_tool_outputs_required');
154
+ return send([...prior, ...toolMessages], params);
155
+ }
156
+
157
+ async function compact(params = {}) {
158
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
159
+ if (!prior.length) throw new Error('cloudflare_provider_state_required');
160
+ const emitFn = params.emit || options.emit;
161
+ const startedAt = Date.now();
162
+ emit(emitFn, 'context.compaction.started', { provider: 'cloudflare', model: params.model }, params.runId);
163
+ const summary = await send([...prior, {
164
+ role: 'user',
165
+ content: 'Create a compact continuation summary of this conversation. Preserve the objective, decisions, changed files, active plan, unresolved failures, tool results that still matter, and constraints. Do not add new work.',
166
+ }], { ...params, tools: [], maxOutputTokens: Math.min(Number(params.maxOutputTokens) || 4096, 4096) });
167
+ const summaryText = String(summary.output_text || '').trim();
168
+ const compactState = Object.freeze({ messages: [
169
+ ...(params.instructions ? [{ role: 'system', content: String(params.instructions) }] : []),
170
+ { role: 'user', content: 'Prior conversation continuation summary:' },
171
+ { role: 'assistant', content: summaryText },
172
+ ] });
173
+ emit(emitFn, 'context.compaction.completed', {
174
+ provider: 'cloudflare', model: params.model, summary_text: summaryText,
175
+ tokens_before: params.tokensBefore ?? null,
176
+ tokens_after: Math.ceil(summaryText.length / 4),
177
+ duration_ms: Date.now() - startedAt,
178
+ }, params.runId);
179
+ return Object.freeze({ provider: 'cloudflare', output: Object.freeze([]), provider_state: compactState, summary_text: summaryText, usage: summary.usage_delta || null, cost: null });
180
+ }
181
+
182
+ return Object.freeze({ provider: 'cloudflare', create, continueWithToolOutputs, compact });
183
+ }