@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,69 @@
1
+ import { createOpenAIResponsesAdapter } from './openai-responses.js';
2
+ import { createAnthropicMessagesAdapter } from './anthropic-messages.js';
3
+ import { createGeminiGenerateContentAdapter } from './gemini-generate-content.js';
4
+ import { createOllamaChatAdapter } from './ollama-chat.js';
5
+ import { createCloudflareChatAdapter } from './cloudflare-chat.js';
6
+
7
+ export function createProviderAdapter(options = {}) {
8
+ const record = options.modelRecord;
9
+ if (!record?.provider) throw new TypeError('modelRecord.provider is required');
10
+ const provider = String(record.provider).toLowerCase();
11
+
12
+ if (provider === 'openai') {
13
+ return createOpenAIResponsesAdapter({
14
+ apiKey: options.credential?.value,
15
+ modelRecord: record,
16
+ fetchImpl: options.fetchImpl,
17
+ emit: options.emit,
18
+ });
19
+ }
20
+
21
+ if (provider === 'grok' || provider === 'xai') {
22
+ return createOpenAIResponsesAdapter({
23
+ providerId: 'grok',
24
+ baseUrl: 'https://api.x.ai/v1',
25
+ apiKey: options.credential?.value,
26
+ modelRecord: record,
27
+ fetchImpl: options.fetchImpl,
28
+ emit: options.emit,
29
+ });
30
+ }
31
+
32
+ if (provider === 'anthropic') {
33
+ return createAnthropicMessagesAdapter({
34
+ apiKey: options.credential?.value,
35
+ modelRecord: record,
36
+ fetchImpl: options.fetchImpl,
37
+ emit: options.emit,
38
+ });
39
+ }
40
+
41
+ if (provider === 'gemini') {
42
+ return createGeminiGenerateContentAdapter({
43
+ apiKey: options.credential?.value,
44
+ modelRecord: record,
45
+ fetchImpl: options.fetchImpl,
46
+ emit: options.emit,
47
+ });
48
+ }
49
+
50
+ if (provider === 'cloudflare') {
51
+ return createCloudflareChatAdapter({
52
+ credential: options.credential,
53
+ modelRecord: record,
54
+ fetchImpl: options.fetchImpl,
55
+ emit: options.emit,
56
+ });
57
+ }
58
+
59
+ if (provider === 'ollama') {
60
+ return createOllamaChatAdapter({
61
+ endpoint: options.endpoint,
62
+ modelRecord: record,
63
+ fetchImpl: options.fetchImpl,
64
+ emit: options.emit,
65
+ });
66
+ }
67
+
68
+ throw new Error(`interactive_provider_adapter_unavailable:${provider}`);
69
+ }
@@ -0,0 +1,208 @@
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 toolDeclarations(tools = []) {
14
+ return tools.map((tool) => ({
15
+ name: tool.name,
16
+ description: tool.description || '',
17
+ parameters: tool.parameters || { type: 'object', properties: {} },
18
+ }));
19
+ }
20
+ function usageParts(response = {}) {
21
+ const usage = response.usageMetadata || {};
22
+ return {
23
+ input_tokens: integer(usage.promptTokenCount),
24
+ cached_input_tokens: integer(usage.cachedContentTokenCount),
25
+ cache_write_tokens: 0,
26
+ output_tokens: integer(usage.candidatesTokenCount),
27
+ reasoning_tokens: integer(usage.thoughtsTokenCount),
28
+ };
29
+ }
30
+ function cumulative(base, delta) {
31
+ return {
32
+ input_tokens: integer(base?.input_tokens) + delta.input_tokens,
33
+ output_tokens: integer(base?.output_tokens) + delta.output_tokens,
34
+ cached_input_tokens: integer(base?.cached_input_tokens) + delta.cached_input_tokens,
35
+ cache_write_tokens: integer(base?.cache_write_tokens),
36
+ reasoning_tokens: integer(base?.reasoning_tokens) + delta.reasoning_tokens,
37
+ };
38
+ }
39
+ function candidateContent(response = {}) {
40
+ return response?.candidates?.[0]?.content || { role: 'model', parts: [] };
41
+ }
42
+ function outputText(content = {}) {
43
+ return (content.parts || []).filter((part) => typeof part?.text === 'string').map((part) => part.text).join('');
44
+ }
45
+ function toolCalls(content = {}) {
46
+ const calls = [];
47
+ let index = 0;
48
+ for (const part of content.parts || []) {
49
+ if (!part?.functionCall?.name) continue;
50
+ index += 1;
51
+ calls.push({
52
+ call_id: `gcall_${index}_${clean(part.functionCall.name).replace(/[^A-Za-z0-9_-]/g, '_')}`,
53
+ name: part.functionCall.name,
54
+ arguments: JSON.stringify(part.functionCall.args || {}),
55
+ });
56
+ }
57
+ return calls;
58
+ }
59
+
60
+ export function createGeminiGenerateContentAdapter(options = {}) {
61
+ const apiKey = clean(options.apiKey || process.env.GEMINI_API_KEY);
62
+ const fetchImpl = options.fetchImpl || fetch;
63
+ const baseUrl = clean(options.baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
64
+
65
+ async function request(model, body, params = {}) {
66
+ if (!apiKey) throw new Error('gemini_api_key_required');
67
+ const url = `${baseUrl}/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
68
+ const response = await fetchImpl(url, {
69
+ method: 'POST',
70
+ headers: { 'content-type': 'application/json' },
71
+ body: JSON.stringify(body),
72
+ signal: signal(params.timeoutMs || options.timeoutMs),
73
+ });
74
+ const data = await response.json().catch(() => ({}));
75
+ if (!response.ok) {
76
+ const error = new Error(data?.error?.message || data?.message || `Gemini HTTP ${response.status}`);
77
+ error.status = response.status;
78
+ error.provider = 'gemini';
79
+ throw error;
80
+ }
81
+ return data;
82
+ }
83
+
84
+ async function send(contents, params = {}) {
85
+ const record = params.modelRecord || options.modelRecord;
86
+ if (!record || record.provider !== 'gemini') throw new Error('gemini_model_record_required');
87
+ const emitFn = params.emit || options.emit;
88
+ const runId = params.runId;
89
+ const model = record.provider_model_id;
90
+ const declarations = toolDeclarations(params.tools || []);
91
+ const body = {
92
+ contents,
93
+ ...(clean(params.instructions) ? { systemInstruction: { parts: [{ text: String(params.instructions) }] } } : {}),
94
+ ...(declarations.length ? { tools: [{ functionDeclarations: declarations }] } : {}),
95
+ generationConfig: {
96
+ ...(Number(params.maxOutputTokens) > 0 ? { maxOutputTokens: Number(params.maxOutputTokens) } : {}),
97
+ },
98
+ };
99
+
100
+ emit(emitFn, 'model.started', {
101
+ provider: 'gemini',
102
+ model,
103
+ reasoning_effort: params.reasoningEffort || 'auto',
104
+ requested_service_tier: 'default',
105
+ }, runId);
106
+
107
+ let response;
108
+ try {
109
+ response = await request(model, body, params);
110
+ } catch (error) {
111
+ const diagnostic = diagnosticFromError(error, { source: 'gemini', kind: 'provider_error' });
112
+ emit(emitFn, 'error.observed', diagnostic, runId);
113
+ emit(emitFn, 'run.failed', { stage: 'model', provider: 'gemini', model, error: diagnostic }, runId);
114
+ throw error;
115
+ }
116
+
117
+ const content = candidateContent(response);
118
+ const calls = toolCalls(content);
119
+ const delta = usageParts(response);
120
+ const usageSnapshot = createUsageSnapshot({
121
+ current_context: { input_tokens: delta.input_tokens, window_tokens: Number(record.context_window) || 0 },
122
+ cumulative: cumulative(params.cumulativeUsage, delta),
123
+ estimate_kind: 'provider',
124
+ });
125
+ const cost = record.pricing ? calculateModelCost(record, delta, { serviceTier: 'default' }) : null;
126
+ emit(emitFn, 'usage.snapshot', usageSnapshot, runId);
127
+ if (cost) emit(emitFn, 'cost.snapshot', cost, runId);
128
+ emit(emitFn, 'model.completed', {
129
+ provider: 'gemini',
130
+ model,
131
+ response_id: response.responseId || null,
132
+ status: response?.candidates?.[0]?.finishReason || 'completed',
133
+ requested_service_tier: 'default',
134
+ actual_service_tier: 'default',
135
+ }, runId);
136
+
137
+ return Object.freeze({
138
+ provider: 'gemini',
139
+ model,
140
+ response_id: response.responseId || null,
141
+ status: response?.candidates?.[0]?.finishReason || 'completed',
142
+ output_text: outputText(content),
143
+ tool_calls: Object.freeze(calls),
144
+ usage_delta: Object.freeze(delta),
145
+ usage_snapshot: usageSnapshot,
146
+ cost,
147
+ provider_state: Object.freeze({
148
+ contents: [...contents, content],
149
+ pending_tools: calls.map((call) => ({ call_id: call.call_id, name: call.name })),
150
+ }),
151
+ requested_service_tier: 'default',
152
+ actual_service_tier: 'default',
153
+ raw: response,
154
+ });
155
+ }
156
+
157
+ async function create(params = {}) {
158
+ const prior = Array.isArray(params.providerState?.contents) ? params.providerState.contents : [];
159
+ const input = typeof params.input === 'string' ? params.input : JSON.stringify(params.input ?? '');
160
+ return send([...prior, { role: 'user', parts: [{ text: input }] }], params);
161
+ }
162
+
163
+ async function continueWithToolOutputs(params = {}) {
164
+ const prior = Array.isArray(params.providerState?.contents) ? params.providerState.contents : [];
165
+ const pending = Array.isArray(params.providerState?.pending_tools) ? params.providerState.pending_tools : [];
166
+ if (!prior.length) throw new Error('gemini_provider_state_required');
167
+ const byId = new Map(pending.map((row) => [row.call_id, row.name]));
168
+ const parts = (params.toolOutputs || []).map((row) => {
169
+ const callId = row.call_id || row.callId;
170
+ const name = byId.get(callId);
171
+ if (!name) throw new Error(`gemini_tool_call_unknown:${callId}`);
172
+ let responseValue = row.output;
173
+ if (typeof responseValue === 'string') {
174
+ try { responseValue = JSON.parse(responseValue); }
175
+ catch { responseValue = { output: responseValue }; }
176
+ }
177
+ return { functionResponse: { name, response: responseValue ?? {} } };
178
+ });
179
+ if (!parts.length) throw new Error('gemini_tool_outputs_required');
180
+ return send([...prior, { role: 'user', parts }], params);
181
+ }
182
+
183
+ async function compact(params = {}) {
184
+ const prior = Array.isArray(params.providerState?.contents) ? params.providerState.contents : [];
185
+ if (!prior.length) throw new Error('gemini_provider_state_required');
186
+ const emitFn = params.emit || options.emit;
187
+ const startedAt = Date.now();
188
+ emit(emitFn, 'context.compaction.started', { provider: 'gemini', model: params.model }, params.runId);
189
+ const summary = await send([...prior, {
190
+ role: 'user',
191
+ parts: [{ 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.' }],
192
+ }], { ...params, tools: [], maxOutputTokens: Math.min(Number(params.maxOutputTokens) || 4096, 4096) });
193
+ const summaryText = String(summary.output_text || '').trim();
194
+ const compactState = Object.freeze({ contents: [
195
+ { role: 'user', parts: [{ text: 'Prior conversation continuation summary:' }] },
196
+ { role: 'model', parts: [{ text: summaryText }] },
197
+ ], pending_tools: [] });
198
+ emit(emitFn, 'context.compaction.completed', {
199
+ provider: 'gemini', model: params.model, summary_text: summaryText,
200
+ tokens_before: params.tokensBefore ?? null,
201
+ tokens_after: Math.ceil(summaryText.length / 4),
202
+ duration_ms: Date.now() - startedAt,
203
+ }, params.runId);
204
+ return Object.freeze({ provider: 'gemini', output: Object.freeze([]), provider_state: compactState, summary_text: summaryText, usage: summary.usage_delta || null });
205
+ }
206
+
207
+ return Object.freeze({ provider: 'gemini', create, continueWithToolOutputs, compact });
208
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ createOpenAIResponsesAdapter,
3
+ extractOpenAIOutputText,
4
+ extractOpenAIFunctionCalls,
5
+ } from './openai-responses.js';
6
+ export { createAnthropicMessagesAdapter } from './anthropic-messages.js';
7
+ export { createGeminiGenerateContentAdapter } from './gemini-generate-content.js';
8
+ export { createOllamaChatAdapter } from './ollama-chat.js';
9
+ export { createCloudflareChatAdapter } from './cloudflare-chat.js';
10
+ export { createProviderAdapter } from './factory.js';
@@ -0,0 +1,148 @@
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 toolsForOllama(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 callsFromMessage(message = {}) {
20
+ return (message.tool_calls || []).map((call, index) => ({
21
+ call_id: call.id || `ocall_${index + 1}_${clean(call.function?.name)}`,
22
+ name: call.function?.name,
23
+ arguments: typeof call.function?.arguments === 'string'
24
+ ? call.function.arguments
25
+ : JSON.stringify(call.function?.arguments || {}),
26
+ }));
27
+ }
28
+
29
+ export function createOllamaChatAdapter(options = {}) {
30
+ const fetchImpl = options.fetchImpl || fetch;
31
+ const endpoint = clean(options.endpoint || process.env.OLLAMA_BASE_URL || 'http://127.0.0.1:11434').replace(/\/$/, '');
32
+
33
+ async function send(messages, params = {}) {
34
+ const record = params.modelRecord || options.modelRecord;
35
+ if (!record || record.provider !== 'ollama') throw new Error('ollama_model_record_required');
36
+ const emitFn = params.emit || options.emit;
37
+ const model = record.provider_model_id;
38
+ emit(emitFn, 'model.started', { provider: 'ollama', model, reasoning_effort: 'auto', requested_service_tier: 'local' }, params.runId);
39
+
40
+ let response;
41
+ try {
42
+ response = await fetchImpl(endpoint + '/api/chat', {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/json' },
45
+ body: JSON.stringify({
46
+ model,
47
+ stream: false,
48
+ messages,
49
+ ...(params.tools?.length ? { tools: toolsForOllama(params.tools) } : {}),
50
+ ...(Number(params.maxOutputTokens) > 0 ? { options: { num_predict: Number(params.maxOutputTokens) } } : {}),
51
+ }),
52
+ });
53
+ if (!response.ok) throw new Error(`Ollama HTTP ${response.status}`);
54
+ } catch (error) {
55
+ const diagnostic = diagnosticFromError(error, { source: 'ollama', kind: 'provider_error' });
56
+ emit(emitFn, 'error.observed', diagnostic, params.runId);
57
+ emit(emitFn, 'run.failed', { stage: 'model', provider: 'ollama', model, error: diagnostic }, params.runId);
58
+ throw error;
59
+ }
60
+
61
+ const data = await response.json();
62
+ const message = data.message || { role: 'assistant', content: '' };
63
+ const delta = {
64
+ input_tokens: integer(data.prompt_eval_count),
65
+ output_tokens: integer(data.eval_count),
66
+ cached_input_tokens: 0,
67
+ cache_write_tokens: 0,
68
+ reasoning_tokens: 0,
69
+ };
70
+ const base = params.cumulativeUsage || {};
71
+ const usageSnapshot = createUsageSnapshot({
72
+ current_context: { input_tokens: delta.input_tokens, window_tokens: Number(record.context_window) || 0 },
73
+ cumulative: {
74
+ input_tokens: integer(base.input_tokens) + delta.input_tokens,
75
+ output_tokens: integer(base.output_tokens) + delta.output_tokens,
76
+ cached_input_tokens: integer(base.cached_input_tokens),
77
+ cache_write_tokens: integer(base.cache_write_tokens),
78
+ reasoning_tokens: integer(base.reasoning_tokens),
79
+ },
80
+ estimate_kind: 'provider',
81
+ });
82
+ emit(emitFn, 'usage.snapshot', usageSnapshot, params.runId);
83
+ emit(emitFn, 'model.completed', { provider: 'ollama', model, status: data.done ? 'completed' : 'partial', requested_service_tier: 'local', actual_service_tier: 'local' }, params.runId);
84
+
85
+ return Object.freeze({
86
+ provider: 'ollama',
87
+ model,
88
+ response_id: null,
89
+ status: data.done ? 'completed' : 'partial',
90
+ output_text: String(message.content || ''),
91
+ tool_calls: Object.freeze(callsFromMessage(message)),
92
+ usage_delta: Object.freeze(delta),
93
+ usage_snapshot: usageSnapshot,
94
+ cost: null,
95
+ provider_state: Object.freeze({ messages: [...messages, message] }),
96
+ requested_service_tier: 'local',
97
+ actual_service_tier: 'local',
98
+ raw: data,
99
+ });
100
+ }
101
+
102
+ async function create(params = {}) {
103
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
104
+ const messages = [...prior];
105
+ if (clean(params.instructions) && !messages.some((row) => row.role === 'system')) {
106
+ messages.push({ role: 'system', content: String(params.instructions) });
107
+ }
108
+ messages.push({ role: 'user', content: typeof params.input === 'string' ? params.input : JSON.stringify(params.input ?? '') });
109
+ return send(messages, params);
110
+ }
111
+
112
+ async function continueWithToolOutputs(params = {}) {
113
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
114
+ if (!prior.length) throw new Error('ollama_provider_state_required');
115
+ const toolMessages = (params.toolOutputs || []).map((row) => ({
116
+ role: 'tool',
117
+ content: typeof row.output === 'string' ? row.output : JSON.stringify(row.output ?? null),
118
+ }));
119
+ return send([...prior, ...toolMessages], params);
120
+ }
121
+
122
+ async function compact(params = {}) {
123
+ const prior = Array.isArray(params.providerState?.messages) ? params.providerState.messages : [];
124
+ if (!prior.length) throw new Error('ollama_provider_state_required');
125
+ const emitFn = params.emit || options.emit;
126
+ const startedAt = Date.now();
127
+ emit(emitFn, 'context.compaction.started', { provider: 'ollama', model: params.model }, params.runId);
128
+ const summary = await send([...prior, {
129
+ role: 'user',
130
+ 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.',
131
+ }], { ...params, tools: [], maxOutputTokens: Math.min(Number(params.maxOutputTokens) || 4096, 4096) });
132
+ const summaryText = String(summary.output_text || '').trim();
133
+ const messages = [];
134
+ if (params.instructions) messages.push({ role: 'system', content: String(params.instructions) });
135
+ messages.push({ role: 'user', content: 'Prior conversation continuation summary:' });
136
+ messages.push({ role: 'assistant', content: summaryText });
137
+ const compactState = Object.freeze({ messages });
138
+ emit(emitFn, 'context.compaction.completed', {
139
+ provider: 'ollama', model: params.model, summary_text: summaryText,
140
+ tokens_before: params.tokensBefore ?? null,
141
+ tokens_after: Math.ceil(summaryText.length / 4),
142
+ duration_ms: Date.now() - startedAt,
143
+ }, params.runId);
144
+ return Object.freeze({ provider: 'ollama', output: Object.freeze([]), provider_state: compactState, summary_text: summaryText, usage: summary.usage_delta || null });
145
+ }
146
+
147
+ return Object.freeze({ provider: 'ollama', create, continueWithToolOutputs, compact });
148
+ }