@inneranimalmedia/agentsam-sdk 2.6.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 (124) hide show
  1. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  2. package/docs/RELEASES.md +7 -7
  3. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  4. package/docs/TEST_TIERS.md +26 -0
  5. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  6. package/package.json +28 -7
  7. package/packages/agentsam-repository/README.md +15 -0
  8. package/packages/agentsam-repository/package.json +25 -0
  9. package/packages/agentsam-repository/src/contracts.js +113 -0
  10. package/packages/agentsam-repository/src/index.js +3 -0
  11. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  12. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  13. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  14. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  15. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  16. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  17. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  18. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  19. package/packages/identity/package.json +1 -1
  20. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  21. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  22. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  23. package/protocol/repository/repository-contract.schema.json +24 -0
  24. package/protocol/repository/repository-dependency.schema.json +24 -0
  25. package/protocol/repository/repository-identity.schema.json +17 -0
  26. package/protocol/rpc/v1/common.proto +16 -0
  27. package/protocol/rpc/v1/errors.proto +35 -0
  28. package/protocol/rpc/v1/knowledge.proto +77 -0
  29. package/services/knowledge/package-lock.json +333 -0
  30. package/services/knowledge/package.json +5 -1
  31. package/src/agent/responses-runner.js +63 -35
  32. package/src/capabilities/repository-snapshot.js +3 -3
  33. package/src/cli.js +23 -6
  34. package/src/commands/context-economics.js +17 -2
  35. package/src/commands/context.js +1 -1
  36. package/src/commands/db.js +20 -3
  37. package/src/commands/env.js +90 -0
  38. package/src/commands/knowledge.js +12 -4
  39. package/src/commands/merkle-persist.js +30 -11
  40. package/src/commands/merkle.js +1 -1
  41. package/src/commands/models.js +123 -65
  42. package/src/commands/ollama.js +26 -0
  43. package/src/commands/preferences.js +53 -26
  44. package/src/commands/shell.js +236 -48
  45. package/src/errors/contract.js +236 -0
  46. package/src/errors/index.js +14 -0
  47. package/src/index.js +13 -1
  48. package/src/knowledge/service/auth.js +13 -0
  49. package/src/knowledge/service/grpc-client.js +115 -0
  50. package/src/knowledge/service/grpc-codec.js +237 -0
  51. package/src/knowledge/service/grpc-server.js +83 -0
  52. package/src/knowledge/service/job-engine.js +248 -0
  53. package/src/knowledge/service/server.js +87 -135
  54. package/src/knowledge/source.js +1 -1
  55. package/src/lib/cli-preferences.js +31 -4
  56. package/src/lib/deploy-receipt/index.js +2 -2
  57. package/src/lib/knowledge-docker.js +6 -3
  58. package/src/lib/local-sessions.js +23 -2
  59. package/src/lib/local-status.js +1 -1
  60. package/src/lib/project-config.js +1 -1
  61. package/src/lib/provider-credentials.js +105 -5
  62. package/src/lib/slash-commands.js +4 -3
  63. package/src/local/migrations.js +93 -0
  64. package/src/local/runtime-store.js +141 -0
  65. package/src/local/sqlite.js +2 -0
  66. package/src/local-pty/server.js +113 -51
  67. package/src/models/discovery.js +292 -0
  68. package/src/providers/anthropic-messages.js +192 -0
  69. package/src/providers/cloudflare-chat.js +183 -0
  70. package/src/providers/factory.js +69 -0
  71. package/src/providers/gemini-generate-content.js +208 -0
  72. package/src/providers/index.js +5 -0
  73. package/src/providers/ollama-chat.js +148 -0
  74. package/src/providers/openai-responses.js +226 -75
  75. package/src/repository/index.js +14 -2
  76. package/src/rpc/generated/common_grpc_pb.js +1 -0
  77. package/src/rpc/generated/common_pb.js +536 -0
  78. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  79. package/src/rpc/generated/errors_pb.js +482 -0
  80. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  81. package/src/rpc/generated/knowledge_pb.js +2168 -0
  82. package/src/rpc/generated/package.json +3 -0
  83. package/src/security/trust-boundary.js +2 -2
  84. package/src/telemetry/events.js +4 -1
  85. package/src/ui/cli/activity.js +76 -0
  86. package/src/ui/cli/compaction.js +15 -0
  87. package/src/ui/cli/footer.js +39 -0
  88. package/src/ui/cli/help.js +192 -0
  89. package/src/ui/cli/plan.js +20 -0
  90. package/src/ui/cli/runtime-events.js +110 -0
  91. package/src/ui/cli/waiting.js +16 -0
  92. package/src/ui/merkle/render.js +1 -1
  93. package/test/cli/preferences-runtime.test.mjs +11 -0
  94. package/test/cli/runtime-ui.test.mjs +74 -0
  95. package/test/error-diagnostics.test.mjs +57 -1
  96. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  97. package/test/integration/cli-help.test.mjs +37 -0
  98. package/test/integration/knowledge-rpc.test.mjs +112 -0
  99. package/test/integration/merkle-cli.test.mjs +61 -0
  100. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  101. package/test/integration/provider-env-cli.test.mjs +49 -0
  102. package/test/integration/provider-factory.test.mjs +197 -0
  103. package/test/integration/repository-company-graph.test.mjs +90 -0
  104. package/test/integration/runtime-migrations.test.mjs +82 -0
  105. package/test/knowledge-service.test.mjs +5 -0
  106. package/test/knowledge.test.mjs +16 -0
  107. package/test/live/terminal-transport.live.test.mjs +24 -0
  108. package/test/local-sessions.test.mjs +7 -1
  109. package/test/models.test.mjs +101 -4
  110. package/test/ollama.test.mjs +21 -0
  111. package/test/portable-context.test.mjs +1 -1
  112. package/test/provider-credentials.test.mjs +45 -1
  113. package/test/release-hygiene.test.mjs +13 -5
  114. package/test/responses-runner.test.mjs +3 -1
  115. package/test/shell.test.mjs +50 -8
  116. package/test/terminal/local-pty.mock.test.mjs +151 -0
  117. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  118. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  119. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  120. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  121. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  122. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  123. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  124. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
@@ -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
+ }
@@ -5,8 +5,14 @@ import { createOpenAIHttpError, diagnosticFromError } from '../errors/index.js';
5
5
  const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
6
6
 
7
7
  function clean(value) { return value == null ? '' : String(value).trim(); }
8
- function integer(value) { const n = Number(value ?? 0); return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0; }
9
-
8
+ function integer(value) {
9
+ const n = Number(value ?? 0);
10
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
11
+ }
12
+ function optionalSignal(timeoutMs) {
13
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return undefined;
14
+ return typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(Math.floor(timeoutMs)) : undefined;
15
+ }
10
16
  function normalizeServiceTier(value, requested = 'default') {
11
17
  const tier = clean(value).toLowerCase();
12
18
  if (tier === 'priority') return 'fast';
@@ -14,6 +20,38 @@ function normalizeServiceTier(value, requested = 'default') {
14
20
  return tier;
15
21
  }
16
22
 
23
+ function historyItems(input) {
24
+ if (input == null) return [];
25
+ if (Array.isArray(input)) return structuredClone(input);
26
+ if (typeof input === 'string') return [{ role: 'user', content: input }];
27
+ return [structuredClone(input)];
28
+ }
29
+
30
+ function xaiCompactionHistory(params = {}, response = {}) {
31
+ const prior = Array.isArray(params.providerState?.compaction_input)
32
+ ? structuredClone(params.providerState.compaction_input)
33
+ : [];
34
+ const current = historyItems(params.input);
35
+ const currentAlreadyStartsWithPrior = prior.length > 0
36
+ && current.length >= prior.length
37
+ && JSON.stringify(current.slice(0, prior.length)) === JSON.stringify(prior);
38
+ if (prior.length) {
39
+ return [
40
+ ...(currentAlreadyStartsWithPrior ? [] : prior),
41
+ ...current,
42
+ ...structuredClone(response.output || []),
43
+ ];
44
+ }
45
+ const prefix = clean(params.instructions)
46
+ ? [{ role: 'system', content: String(params.instructions) }]
47
+ : [];
48
+ return [
49
+ ...prefix,
50
+ ...current,
51
+ ...structuredClone(response.output || []),
52
+ ];
53
+ }
54
+
17
55
  export function extractOpenAIOutputText(response = {}) {
18
56
  if (typeof response.output_text === 'string') return response.output_text;
19
57
  const chunks = [];
@@ -27,13 +65,15 @@ export function extractOpenAIOutputText(response = {}) {
27
65
  }
28
66
 
29
67
  export function extractOpenAIFunctionCalls(response = {}) {
30
- return (response.output || []).filter((item) => item?.type === 'function_call').map((item) => Object.freeze({
31
- id: item.id || null,
32
- call_id: item.call_id,
33
- name: item.name,
34
- arguments: item.arguments || '{}',
35
- status: item.status || null,
36
- }));
68
+ return (response.output || [])
69
+ .filter((item) => item?.type === 'function_call')
70
+ .map((item) => Object.freeze({
71
+ id: item.id || null,
72
+ call_id: item.call_id,
73
+ name: item.name,
74
+ arguments: item.arguments || '{}',
75
+ status: item.status || null,
76
+ }));
37
77
  }
38
78
 
39
79
  function usageParts(response = {}) {
@@ -47,20 +87,12 @@ function usageParts(response = {}) {
47
87
  };
48
88
  }
49
89
 
50
- function errorMessage(body, status) {
51
- return body?.error?.message || body?.message || `OpenAI Responses API returned HTTP ${status}`;
52
- }
53
-
54
- function optionalSignal(timeoutMs) {
55
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return undefined;
56
- if (typeof AbortSignal?.timeout !== 'function') return undefined;
57
- return AbortSignal.timeout(Math.floor(timeoutMs));
58
- }
59
-
60
90
  function normalizeTools(tools = []) {
61
91
  if (!Array.isArray(tools)) throw new TypeError('tools must be an array');
62
92
  return tools.map((tool) => {
63
- if (tool?.type !== 'function' || !clean(tool.name)) throw new TypeError('OpenAI adapter tools must be Responses function tool descriptors');
93
+ if (tool?.type !== 'function' || !clean(tool.name)) {
94
+ throw new TypeError('OpenAI-compatible adapter tools must be Responses function tool descriptors');
95
+ }
64
96
  return {
65
97
  type: 'function',
66
98
  name: clean(tool.name),
@@ -72,11 +104,23 @@ function normalizeTools(tools = []) {
72
104
  });
73
105
  }
74
106
 
75
- function assertRuntimeConfig(model, reasoningEffort, serviceTier) {
76
- const record = getModelRecord(model);
77
- if (!record || record.provider !== 'openai') throw new RangeError(`unsupported OpenAI model catalog entry: ${model}`);
78
- if (!record.reasoning_efforts.includes(reasoningEffort)) throw new RangeError(`unsupported reasoning effort for ${record.provider_model_id}: ${reasoningEffort}`);
79
- if (!record.service_tiers.includes(serviceTier)) throw new RangeError(`unsupported service tier for ${record.provider_model_id}: ${serviceTier}`);
107
+ function assertRuntimeConfig(model, reasoningEffort, serviceTier, explicitRecord, expectedProvider) {
108
+ const record = explicitRecord || getModelRecord(model);
109
+ if (!record || record.provider !== expectedProvider) {
110
+ throw new RangeError(`unsupported ${expectedProvider} model record: ${model}`);
111
+ }
112
+ const reasoning = Array.isArray(record.reasoning_efforts) && record.reasoning_efforts.length
113
+ ? record.reasoning_efforts
114
+ : ['auto'];
115
+ const tiers = Array.isArray(record.service_tiers) && record.service_tiers.length
116
+ ? record.service_tiers
117
+ : ['default'];
118
+ if (reasoningEffort !== 'auto' && !reasoning.includes(reasoningEffort)) {
119
+ throw new RangeError(`unsupported reasoning effort for ${record.provider_model_id}: ${reasoningEffort}`);
120
+ }
121
+ if (!tiers.includes(serviceTier)) {
122
+ throw new RangeError(`unsupported service tier for ${record.provider_model_id}: ${serviceTier}`);
123
+ }
80
124
  return record;
81
125
  }
82
126
 
@@ -85,21 +129,38 @@ function emitEvent(emit, type, payload, meta = {}) {
85
129
  emit(createAgentEvent(type, payload, meta));
86
130
  }
87
131
 
132
+ function genericHttpError(providerId, response, parsed, rawText) {
133
+ const error = new Error(
134
+ clean(parsed?.error?.message || parsed?.message) ||
135
+ `${providerId} Responses API returned HTTP ${response.status}`,
136
+ );
137
+ error.status = response.status;
138
+ error.provider = providerId;
139
+ error.body = parsed;
140
+ error.rawText = rawText;
141
+ return error;
142
+ }
143
+
88
144
  export function createOpenAIResponsesAdapter(options = {}) {
89
- const apiKey = clean(options.apiKey || process.env.OPENAI_API_KEY);
145
+ const providerId = clean(options.providerId || 'openai').toLowerCase();
146
+ const apiKey = clean(
147
+ options.apiKey ||
148
+ (providerId === 'openai' ? process.env.OPENAI_API_KEY : providerId === 'grok' ? process.env.XAI_API_KEY : ''),
149
+ );
90
150
  const baseUrl = clean(options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
91
151
  const fetchImpl = options.fetchImpl || fetch;
92
152
  const defaultEmit = options.emit;
93
153
  const timeoutMs = options.timeoutMs;
94
154
 
95
155
  async function request(pathname, body, runtime = {}) {
96
- if (!apiKey) throw new Error('OPENAI_API_KEY is required for OpenAI Responses execution');
156
+ if (!apiKey) throw new Error(`${providerId}_api_key_required`);
97
157
  const response = await fetchImpl(`${baseUrl}${pathname}`, {
98
158
  method: 'POST',
99
159
  headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
100
160
  body: JSON.stringify(body),
101
161
  signal: optionalSignal(runtime.timeoutMs ?? timeoutMs),
102
162
  });
163
+
103
164
  let rawText = '';
104
165
  let parsed = null;
105
166
  if (typeof response.text === 'function') {
@@ -113,49 +174,67 @@ export function createOpenAIResponsesAdapter(options = {}) {
113
174
  rawText = JSON.stringify(parsed ?? null);
114
175
  } catch {
115
176
  parsed = null;
116
- rawText = '';
117
177
  }
118
178
  }
179
+
119
180
  if (!response.ok) {
120
- throw createOpenAIHttpError({
121
- status: response.status,
122
- body: parsed,
123
- rawText,
124
- headers: response.headers,
125
- requestedServiceTier: body?.service_tier,
126
- });
181
+ if (providerId === 'openai') {
182
+ throw createOpenAIHttpError({
183
+ status: response.status,
184
+ body: parsed,
185
+ rawText,
186
+ headers: response.headers,
187
+ requestedServiceTier: body?.service_tier,
188
+ });
189
+ }
190
+ throw genericHttpError(providerId, response, parsed, rawText);
127
191
  }
192
+
128
193
  return Object.freeze({
129
194
  data: parsed ?? {},
130
195
  http: Object.freeze({
131
196
  status: response.status,
132
- request_id: clean(response.headers?.get?.('x-request-id') || response.headers?.get?.('openai-request-id')) || null,
197
+ request_id: clean(
198
+ response.headers?.get?.('x-request-id') ||
199
+ response.headers?.get?.('openai-request-id'),
200
+ ) || null,
133
201
  ray_id: clean(response.headers?.get?.('cf-ray')) || null,
134
202
  }),
135
203
  });
136
204
  }
137
205
 
138
206
  async function create(params = {}) {
139
- const modelRecord = getModelRecord(params.model);
207
+ const suppliedRecord = params.modelRecord || options.modelRecord || null;
208
+ const modelRecord = suppliedRecord || getModelRecord(params.model);
140
209
  const model = modelRecord?.provider_model_id || clean(params.model);
141
- const reasoningEffort = clean(params.reasoningEffort || params.reasoning_effort || 'low');
210
+ const reasoningEffort = clean(params.reasoningEffort || params.reasoning_effort || 'auto');
142
211
  const serviceTier = clean(params.serviceTier || params.service_tier || 'default');
143
- const record = assertRuntimeConfig(model, reasoningEffort, serviceTier);
212
+ const record = assertRuntimeConfig(model, reasoningEffort, serviceTier, modelRecord, providerId);
144
213
  const emit = params.emit || defaultEmit;
145
214
  const meta = { runId: params.runId, sequence: params.sequence };
146
215
  const tools = normalizeTools(params.tools || []);
216
+ const previousResponseId = clean(params.previousResponseId || params.providerState?.previous_response_id);
217
+ const requestInput = providerId === 'grok'
218
+ && !previousResponseId
219
+ && Array.isArray(params.providerState?.compaction_input)
220
+ && params.providerState.compaction_input.length
221
+ ? [...structuredClone(params.providerState.compaction_input), ...historyItems(params.input)]
222
+ : params.input ?? '';
223
+
147
224
  const body = {
148
225
  model,
149
- input: params.input ?? '',
150
- reasoning: { effort: reasoningEffort },
151
- service_tier: serviceTier,
226
+ input: requestInput,
227
+ ...(reasoningEffort !== 'auto' ? { reasoning: { effort: reasoningEffort } } : {}),
228
+ ...(providerId === 'openai' || serviceTier !== 'default' ? { service_tier: serviceTier } : {}),
152
229
  store: params.store !== false,
153
230
  truncation: 'disabled',
154
231
  parallel_tool_calls: params.parallelToolCalls !== false,
155
232
  ...(clean(params.instructions) ? { instructions: String(params.instructions) } : {}),
156
233
  ...(tools.length ? { tools } : {}),
157
- ...(clean(params.previousResponseId) ? { previous_response_id: clean(params.previousResponseId) } : {}),
158
- ...(Number.isInteger(params.maxOutputTokens) && params.maxOutputTokens > 0 ? { max_output_tokens: params.maxOutputTokens } : {}),
234
+ ...(previousResponseId ? { previous_response_id: previousResponseId } : {}),
235
+ ...(Number.isInteger(params.maxOutputTokens) && params.maxOutputTokens > 0
236
+ ? { max_output_tokens: params.maxOutputTokens }
237
+ : {}),
159
238
  ...(clean(params.promptCacheKey) ? { prompt_cache_key: clean(params.promptCacheKey) } : {}),
160
239
  ...(params.promptCacheOptions ? { prompt_cache_options: params.promptCacheOptions } : {}),
161
240
  ...(params.metadata ? { metadata: params.metadata } : {}),
@@ -163,7 +242,10 @@ export function createOpenAIResponsesAdapter(options = {}) {
163
242
  };
164
243
 
165
244
  emitEvent(emit, 'model.started', {
166
- provider: 'openai', model, reasoning_effort: reasoningEffort, requested_service_tier: serviceTier,
245
+ provider: providerId,
246
+ model,
247
+ reasoning_effort: reasoningEffort,
248
+ requested_service_tier: serviceTier,
167
249
  }, meta);
168
250
 
169
251
  let response;
@@ -173,17 +255,22 @@ export function createOpenAIResponsesAdapter(options = {}) {
173
255
  response = result.data;
174
256
  http = result.http;
175
257
  } catch (error) {
176
- const diagnostic = diagnosticFromError(error, { source: 'openai', kind: 'provider_error' });
258
+ const diagnostic = diagnosticFromError(error, { source: providerId, kind: 'provider_error' });
177
259
  emitEvent(emit, 'error.observed', diagnostic, meta);
178
- emitEvent(emit, 'run.failed', { stage: 'model', provider: 'openai', model, error: diagnostic }, meta);
260
+ emitEvent(emit, 'run.failed', { stage: 'model', provider: providerId, model, error: diagnostic }, meta);
179
261
  throw error;
180
262
  }
181
263
 
182
264
  const delta = usageParts(response);
183
265
  const actualServiceTier = normalizeServiceTier(response.service_tier, serviceTier);
184
- const cost = calculateModelCost(record, { ...delta, estimate_kind: 'provider' }, { serviceTier: actualServiceTier });
266
+ const cost = record.pricing
267
+ ? calculateModelCost(record, { ...delta, estimate_kind: 'provider' }, { serviceTier: actualServiceTier })
268
+ : null;
185
269
  const usageSnapshot = createUsageSnapshot({
186
- current_context: { input_tokens: delta.input_tokens, window_tokens: record.context_window },
270
+ current_context: {
271
+ input_tokens: delta.input_tokens,
272
+ window_tokens: Number(record.context_window) || 0,
273
+ },
187
274
  cumulative: params.cumulativeUsage ? {
188
275
  input_tokens: integer(params.cumulativeUsage.input_tokens) + delta.input_tokens,
189
276
  output_tokens: integer(params.cumulativeUsage.output_tokens) + delta.output_tokens,
@@ -195,18 +282,30 @@ export function createOpenAIResponsesAdapter(options = {}) {
195
282
  });
196
283
 
197
284
  emitEvent(emit, 'usage.snapshot', usageSnapshot, meta);
198
- emitEvent(emit, 'cost.snapshot', cost, meta);
285
+ if (cost) emitEvent(emit, 'cost.snapshot', cost, meta);
199
286
  emitEvent(emit, 'model.completed', {
200
- provider: 'openai', model, response_id: response.id, status: response.status,
201
- request_id: http?.request_id || null, ray_id: http?.ray_id || null,
202
- requested_service_tier: serviceTier, actual_service_tier: actualServiceTier,
287
+ provider: providerId,
288
+ model,
289
+ response_id: response.id,
290
+ status: response.status,
291
+ request_id: http?.request_id || null,
292
+ ray_id: http?.ray_id || null,
293
+ requested_service_tier: serviceTier,
294
+ actual_service_tier: actualServiceTier,
203
295
  }, meta);
204
296
 
297
+ const providerState = providerId === 'grok'
298
+ ? Object.freeze({
299
+ previous_response_id: response.id || null,
300
+ compaction_input: xaiCompactionHistory(params, response),
301
+ })
302
+ : Object.freeze({ previous_response_id: response.id || null });
303
+
205
304
  return Object.freeze({
206
- provider: 'openai',
305
+ provider: providerId,
207
306
  model,
208
- response_id: response.id,
209
- status: response.status,
307
+ response_id: response.id || null,
308
+ status: response.status || 'completed',
210
309
  request_id: http?.request_id || null,
211
310
  ray_id: http?.ray_id || null,
212
311
  output_text: extractOpenAIOutputText(response),
@@ -214,6 +313,7 @@ export function createOpenAIResponsesAdapter(options = {}) {
214
313
  usage_delta: Object.freeze(delta),
215
314
  usage_snapshot: usageSnapshot,
216
315
  cost,
316
+ provider_state: providerState,
217
317
  requested_service_tier: serviceTier,
218
318
  actual_service_tier: actualServiceTier,
219
319
  raw: response,
@@ -221,7 +321,7 @@ export function createOpenAIResponsesAdapter(options = {}) {
221
321
  }
222
322
 
223
323
  async function continueWithToolOutputs(params = {}) {
224
- const previousResponseId = clean(params.previousResponseId);
324
+ const previousResponseId = clean(params.previousResponseId || params.providerState?.previous_response_id);
225
325
  if (!previousResponseId) throw new TypeError('previousResponseId is required');
226
326
  const outputs = (params.toolOutputs || []).map((row) => {
227
327
  const callId = clean(row.call_id || row.callId);
@@ -234,20 +334,43 @@ export function createOpenAIResponsesAdapter(options = {}) {
234
334
  }
235
335
 
236
336
  async function compact(params = {}) {
237
- const modelRecord = getModelRecord(params.model);
337
+ if (providerId !== 'openai' && providerId !== 'grok') throw new Error(`native_compaction_unavailable:${providerId}`);
338
+ const modelRecord = params.modelRecord || options.modelRecord || getModelRecord(params.model);
238
339
  const model = modelRecord?.provider_model_id || clean(params.model);
239
- if (!modelRecord || modelRecord.provider !== 'openai') throw new RangeError(`unsupported OpenAI model catalog entry: ${params.model}`);
340
+ if (!modelRecord || modelRecord.provider !== providerId) {
341
+ throw new RangeError(`unsupported ${providerId} model record: ${params.model}`);
342
+ }
343
+
240
344
  const emit = params.emit || defaultEmit;
241
345
  const meta = { runId: params.runId, sequence: params.sequence };
242
- const body = {
346
+ const previousResponseId = clean(params.previousResponseId || params.providerState?.previous_response_id);
347
+ const xaiHistory = Array.isArray(params.providerState?.compaction_input)
348
+ ? structuredClone(params.providerState.compaction_input)
349
+ : [];
350
+ if (providerId === 'grok' && !xaiHistory.length) {
351
+ throw new Error('xai_compaction_history_unavailable');
352
+ }
353
+ const body = providerId === 'grok'
354
+ ? {
355
+ model,
356
+ input: xaiHistory,
357
+ }
358
+ : {
359
+ model,
360
+ ...(previousResponseId ? { previous_response_id: previousResponseId } : {}),
361
+ ...(params.input != null ? { input: params.input } : {}),
362
+ ...(clean(params.instructions) ? { instructions: String(params.instructions) } : {}),
363
+ ...(clean(params.promptCacheKey) ? { prompt_cache_key: clean(params.promptCacheKey) } : {}),
364
+ ...(params.promptCacheOptions ? { prompt_cache_options: params.promptCacheOptions } : {}),
365
+ };
366
+
367
+ const compactStartedAt = Date.now();
368
+ emitEvent(emit, 'context.compaction.started', {
369
+ provider: providerId,
243
370
  model,
244
- ...(clean(params.previousResponseId) ? { previous_response_id: clean(params.previousResponseId) } : {}),
245
- ...(params.input != null ? { input: params.input } : {}),
246
- ...(clean(params.instructions) ? { instructions: String(params.instructions) } : {}),
247
- ...(clean(params.promptCacheKey) ? { prompt_cache_key: clean(params.promptCacheKey) } : {}),
248
- ...(params.promptCacheOptions ? { prompt_cache_options: params.promptCacheOptions } : {}),
249
- };
250
- emitEvent(emit, 'context.compaction.started', { provider: 'openai', model, previous_response_id: body.previous_response_id || null }, meta);
371
+ previous_response_id: previousResponseId || null,
372
+ }, meta);
373
+
251
374
  let response;
252
375
  let http;
253
376
  try {
@@ -255,21 +378,49 @@ export function createOpenAIResponsesAdapter(options = {}) {
255
378
  response = result.data;
256
379
  http = result.http;
257
380
  } catch (error) {
258
- const diagnostic = diagnosticFromError(error, { source: 'openai', kind: 'provider_error' });
381
+ const diagnostic = diagnosticFromError(error, { source: providerId, kind: 'provider_error' });
259
382
  emitEvent(emit, 'error.observed', diagnostic, meta);
260
- emitEvent(emit, 'run.failed', { stage: 'compaction', provider: 'openai', model, error: diagnostic }, meta);
383
+ emitEvent(emit, 'run.failed', { stage: 'compaction', provider: providerId, model, error: diagnostic }, meta);
261
384
  throw error;
262
385
  }
386
+
387
+ const compactUsage = usageParts(response);
388
+ const compactCost = modelRecord.pricing
389
+ ? calculateModelCost(modelRecord, { ...compactUsage, estimate_kind: 'provider' }, { serviceTier: 'default' })
390
+ : null;
391
+ if (compactCost) emitEvent(emit, 'cost.snapshot', compactCost, meta);
263
392
  emitEvent(emit, 'context.compaction.completed', {
264
- provider: 'openai', model, compaction_id: response.id, usage: response.usage || null,
265
- request_id: http?.request_id || null, ray_id: http?.ray_id || null,
393
+ provider: providerId,
394
+ model,
395
+ compaction_id: response.id,
396
+ usage: response.usage || null,
397
+ tokens_before: Number(params.tokensBefore || compactUsage.input_tokens || 0),
398
+ tokens_after: Number(compactUsage.output_tokens || 0),
399
+ duration_ms: Date.now() - compactStartedAt,
400
+ request_id: http?.request_id || null,
401
+ ray_id: http?.ray_id || null,
266
402
  }, meta);
403
+
267
404
  return Object.freeze({
268
- provider: 'openai', model, compaction_id: response.id,
269
- request_id: http?.request_id || null, ray_id: http?.ray_id || null,
270
- output: Object.freeze(response.output || []), usage: response.usage || null, raw: response,
405
+ provider: providerId,
406
+ model,
407
+ compaction_id: response.id,
408
+ request_id: http?.request_id || null,
409
+ ray_id: http?.ray_id || null,
410
+ output: Object.freeze(response.output || []),
411
+ provider_state: providerId === 'grok'
412
+ ? Object.freeze({ previous_response_id: null, compaction_input: structuredClone(response.output || []) })
413
+ : Object.freeze({ previous_response_id: null }),
414
+ usage: response.usage || null,
415
+ cost: compactCost,
416
+ raw: response,
271
417
  });
272
418
  }
273
419
 
274
- return Object.freeze({ provider: 'openai', create, continueWithToolOutputs, compact });
420
+ return Object.freeze({
421
+ provider: providerId,
422
+ create,
423
+ continueWithToolOutputs,
424
+ ...(['openai', 'grok'].includes(providerId) ? { compact } : {}),
425
+ });
275
426
  }
@@ -1,4 +1,16 @@
1
1
  export { repositorySnapshot } from '../capabilities/repository-snapshot.js';
2
- export { buildMerkleTree, diffTrees, readSnapshot, saveSnapshot, validateSnapshot } from '../lib/merkle/index.js';
3
- export { resolveGitContext, tryResolveGitContext, normalizeGitRemote } from '../lib/git-context.js';
2
+ export { buildMerkleTree, diffTrees, readSnapshot, saveSnapshot, validateSnapshot } from '../../packages/agentsam-repository/src/merkle/index.js';
3
+ export { resolveGitContext, tryResolveGitContext, normalizeGitRemote } from '../../packages/agentsam-repository/src/git-context.js';
4
4
  export { buildRetrievalPlan, createContextPack } from '../knowledge/index.js';
5
+ export {
6
+ COMPANY_REPOSITORY_GRAPH_SCHEMA_VERSION,
7
+ REPOSITORY_STATUSES,
8
+ REPOSITORY_CONTRACT_TYPES,
9
+ REPOSITORY_CONTRACT_STATUSES,
10
+ REPOSITORY_DEPENDENCY_TYPES,
11
+ REPOSITORY_DEPENDENCY_CRITICALITIES,
12
+ REPOSITORY_FAILURE_POLICIES,
13
+ createRepositoryIdentity,
14
+ createRepositoryContract,
15
+ createRepositoryDependency,
16
+ } from '../../packages/agentsam-repository/src/contracts.js';
@@ -0,0 +1 @@
1
+ // GENERATED CODE -- NO SERVICES IN PROTO