@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.
- package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +7 -7
- package/docs/SOURCE_ARCHITECTURE.md +58 -0
- package/docs/TEST_TIERS.md +26 -0
- package/migrations/runtime/0001_cli_runtime.sql +298 -0
- package/package.json +28 -7
- package/packages/agentsam-repository/README.md +15 -0
- package/packages/agentsam-repository/package.json +25 -0
- package/packages/agentsam-repository/src/contracts.js +113 -0
- package/packages/agentsam-repository/src/index.js +3 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
- package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
- package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
- package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
- package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
- package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
- package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
- package/packages/identity/package.json +1 -1
- package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
- package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
- package/protocol/repository/repository-contract.schema.json +24 -0
- package/protocol/repository/repository-dependency.schema.json +24 -0
- package/protocol/repository/repository-identity.schema.json +17 -0
- package/protocol/rpc/v1/common.proto +16 -0
- package/protocol/rpc/v1/errors.proto +35 -0
- package/protocol/rpc/v1/knowledge.proto +77 -0
- package/services/knowledge/package-lock.json +333 -0
- package/services/knowledge/package.json +5 -1
- package/src/agent/responses-runner.js +63 -35
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +23 -6
- package/src/commands/context-economics.js +17 -2
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/env.js +90 -0
- package/src/commands/knowledge.js +12 -4
- package/src/commands/merkle-persist.js +30 -11
- package/src/commands/merkle.js +1 -1
- package/src/commands/models.js +123 -65
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +53 -26
- package/src/commands/shell.js +236 -48
- package/src/errors/contract.js +236 -0
- package/src/errors/index.js +14 -0
- package/src/index.js +13 -1
- package/src/knowledge/service/auth.js +13 -0
- package/src/knowledge/service/grpc-client.js +115 -0
- package/src/knowledge/service/grpc-codec.js +237 -0
- package/src/knowledge/service/grpc-server.js +83 -0
- package/src/knowledge/service/job-engine.js +248 -0
- package/src/knowledge/service/server.js +87 -135
- package/src/knowledge/source.js +1 -1
- package/src/lib/cli-preferences.js +31 -4
- package/src/lib/deploy-receipt/index.js +2 -2
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +23 -2
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +105 -5
- package/src/lib/slash-commands.js +4 -3
- package/src/local/migrations.js +93 -0
- package/src/local/runtime-store.js +141 -0
- package/src/local/sqlite.js +2 -0
- package/src/local-pty/server.js +113 -51
- package/src/models/discovery.js +292 -0
- package/src/providers/anthropic-messages.js +192 -0
- package/src/providers/cloudflare-chat.js +183 -0
- package/src/providers/factory.js +69 -0
- package/src/providers/gemini-generate-content.js +208 -0
- package/src/providers/index.js +5 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +226 -75
- package/src/repository/index.js +14 -2
- package/src/rpc/generated/common_grpc_pb.js +1 -0
- package/src/rpc/generated/common_pb.js +536 -0
- package/src/rpc/generated/errors_grpc_pb.js +1 -0
- package/src/rpc/generated/errors_pb.js +482 -0
- package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
- package/src/rpc/generated/knowledge_pb.js +2168 -0
- package/src/rpc/generated/package.json +3 -0
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/events.js +4 -1
- package/src/ui/cli/activity.js +76 -0
- package/src/ui/cli/compaction.js +15 -0
- package/src/ui/cli/footer.js +39 -0
- package/src/ui/cli/help.js +192 -0
- package/src/ui/cli/plan.js +20 -0
- package/src/ui/cli/runtime-events.js +110 -0
- package/src/ui/cli/waiting.js +16 -0
- package/src/ui/merkle/render.js +1 -1
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -0
- package/test/error-diagnostics.test.mjs +57 -1
- package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
- package/test/integration/cli-help.test.mjs +37 -0
- package/test/integration/knowledge-rpc.test.mjs +112 -0
- package/test/integration/merkle-cli.test.mjs +61 -0
- package/test/integration/merkle-persistence-identity.test.mjs +48 -0
- package/test/integration/provider-env-cli.test.mjs +49 -0
- package/test/integration/provider-factory.test.mjs +197 -0
- package/test/integration/repository-company-graph.test.mjs +90 -0
- package/test/integration/runtime-migrations.test.mjs +82 -0
- package/test/knowledge-service.test.mjs +5 -0
- package/test/knowledge.test.mjs +16 -0
- package/test/live/terminal-transport.live.test.mjs +24 -0
- package/test/local-sessions.test.mjs +7 -1
- package/test/models.test.mjs +101 -4
- package/test/ollama.test.mjs +21 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +45 -1
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +3 -1
- package/test/shell.test.mjs +50 -8
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
package/src/commands/shell.js
CHANGED
|
@@ -12,17 +12,25 @@ import { runStatus } from './status.js';
|
|
|
12
12
|
import { runModels } from './models.js';
|
|
13
13
|
import { configureCliPreferences } from './preferences.js';
|
|
14
14
|
import { runCloudflare } from './cloudflare.js';
|
|
15
|
-
import {
|
|
15
|
+
import { probeOllamaModel, resolveOllamaConfig } from './ollama.js';
|
|
16
|
+
import { createInlineActivity } from '../ui/cli/activity.js';
|
|
17
|
+
import { createCliRuntimePresenter } from '../ui/cli/runtime-events.js';
|
|
18
|
+
import { renderCliFooter } from '../ui/cli/footer.js';
|
|
16
19
|
import { diagnosticFromError, renderDiagnosticError } from '../errors/index.js';
|
|
17
20
|
import { getModelRecord } from '../models/index.js';
|
|
21
|
+
import { discoverProviderModels } from '../models/discovery.js';
|
|
18
22
|
import { readCliPreferences, updateCliPreferences } from '../lib/cli-preferences.js';
|
|
19
23
|
import { buildContextEconomicsReport, renderContextEconomics } from './context-economics.js';
|
|
20
|
-
import {
|
|
24
|
+
import { createProviderAdapter } from '../providers/index.js';
|
|
21
25
|
import { createCapabilityAdapter, runResponsesAgent } from '../agent/index.js';
|
|
22
26
|
import { resolveProviderCredential } from '../lib/provider-credentials.js';
|
|
23
|
-
import { createLocalSession, saveLocalSession, sessionTitleFromInput } from '../lib/local-sessions.js';
|
|
27
|
+
import { createLocalSession, saveLocalSession, sessionTitleFromInput, localSessionElapsedMs } from '../lib/local-sessions.js';
|
|
24
28
|
import { grantExecutionApproval, isExecutionApproved, toolApprovalKey } from '../lib/execution-approvals.js';
|
|
25
29
|
import { runWhoami } from './whoami.js';
|
|
30
|
+
import { runLogin, runLogout } from './account-auth.js';
|
|
31
|
+
import { runHelp } from '../ui/cli/help.js';
|
|
32
|
+
import { readAccountSession } from '../lib/account-session.js';
|
|
33
|
+
import { startRuntimeRun, finishRuntimeRun, recordRuntimeCompaction } from '../local/runtime-store.js';
|
|
26
34
|
|
|
27
35
|
function writeLine(write, value = '') { write(`${value}\n`); }
|
|
28
36
|
|
|
@@ -76,18 +84,20 @@ export function tokenizeShellLine(input = '') {
|
|
|
76
84
|
|
|
77
85
|
export function renderShellCatalog() {
|
|
78
86
|
const next = SHELL_PHASES.find((phase) => phase.status === 'next' || phase.status === 'current');
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
return [
|
|
88
|
+
'',
|
|
89
|
+
' Agent Sam',
|
|
90
|
+
' Type normally to work with the selected model.',
|
|
91
|
+
'',
|
|
92
|
+
' / command picker',
|
|
93
|
+
' /model model · reasoning · processing',
|
|
94
|
+
' /usage tokens · cost · resume receipt',
|
|
95
|
+
' /help focused help',
|
|
96
|
+
' /exit return to host shell',
|
|
97
|
+
'',
|
|
98
|
+
` ${next?.label ?? 'interactive runtime'} · ${SLASH_COMMANDS.length} commands available through /`,
|
|
99
|
+
'',
|
|
100
|
+
].join('\n');
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
function parseDeployOptions(args, cwd) {
|
|
@@ -109,11 +119,53 @@ function spawnGit(cwd, args) {
|
|
|
109
119
|
|
|
110
120
|
function selectedModel(cwd) {
|
|
111
121
|
const preferences = readCliPreferences(cwd) || {};
|
|
112
|
-
const
|
|
122
|
+
const snapshot = preferences.modelSnapshot?.model_key === preferences.modelPreference
|
|
123
|
+
? preferences.modelSnapshot
|
|
124
|
+
: null;
|
|
125
|
+
const model = snapshot || getModelRecord(preferences.modelPreference);
|
|
113
126
|
if (!model) throw new Error('Select an exact provider-verified model with /model first so Agent Sam can verify supported runtime controls.');
|
|
114
127
|
return { preferences, model };
|
|
115
128
|
}
|
|
116
129
|
|
|
130
|
+
async function resolveModelForTurn(cwd, state) {
|
|
131
|
+
const selected = selectedModel(cwd);
|
|
132
|
+
const { preferences } = selected;
|
|
133
|
+
let model = selected.model;
|
|
134
|
+
|
|
135
|
+
if (model.provider === 'ollama') {
|
|
136
|
+
const config = resolveOllamaConfig({}, process.env);
|
|
137
|
+
const probe = await probeOllamaModel(model.provider_model_id, config, state.providerFetchImpl || fetch);
|
|
138
|
+
if (!probe.ok) throw new Error(`ollama_model_probe_failed:${model.provider_model_id}:${probe.error || 'unknown'}`);
|
|
139
|
+
model = {
|
|
140
|
+
...model,
|
|
141
|
+
context_window: probe.context_window,
|
|
142
|
+
context_window_source: probe.context_window_source || 'unknown',
|
|
143
|
+
capabilities: { ...(model.capabilities || {}), local_runtime: true, ...(Object.fromEntries((probe.capabilities || []).map((name) => [name, true]))) },
|
|
144
|
+
};
|
|
145
|
+
updateCliPreferences(cwd, { modelPreference: model.model_key, modelSnapshot: model });
|
|
146
|
+
return { preferences: readCliPreferences(cwd) || preferences, model, credential: null, verification: 'local_runtime' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const credential = resolveProviderCredential(model.provider, { home: state.home });
|
|
150
|
+
if (!credential.configured) {
|
|
151
|
+
throw new Error(`provider_credential_unavailable:${model.provider}:${credential.error || credential.env || 'not_configured'}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const discovery = await discoverProviderModels(model.provider, credential, { fetchImpl: state.providerFetchImpl || fetch });
|
|
155
|
+
if (discovery.ok) {
|
|
156
|
+
const live = discovery.models.find((row) => row.provider_model_id === model.provider_model_id || row.model_key === model.model_key);
|
|
157
|
+
if (!live) throw new Error(`selected_model_not_available_for_credential:${model.provider}:${model.provider_model_id}`);
|
|
158
|
+
model = live;
|
|
159
|
+
updateCliPreferences(cwd, { modelPreference: live.model_key, modelSnapshot: live });
|
|
160
|
+
return { preferences: readCliPreferences(cwd) || preferences, model, credential, verification: 'provider_api' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (preferences.modelSnapshot?.availability === 'available') {
|
|
164
|
+
return { preferences, model, credential, verification: 'cached_provider_snapshot', discoveryError: discovery.error || null };
|
|
165
|
+
}
|
|
166
|
+
throw new Error(`provider_model_discovery_failed:${model.provider}:${discovery.error || 'unknown'}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
117
169
|
async function chooseReasoning(cwd, args, state) {
|
|
118
170
|
const { preferences, model } = selectedModel(cwd);
|
|
119
171
|
let effort = String(args[0] || '').trim().toLowerCase();
|
|
@@ -167,7 +219,7 @@ export async function runLocalAgent(goal, write, options = {}) {
|
|
|
167
219
|
}
|
|
168
220
|
const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
169
221
|
const fetchImpl = options.fetchImpl || fetch;
|
|
170
|
-
const activity = options.activity ||
|
|
222
|
+
const activity = options.activity || createInlineActivity({ write, label: 'Thinking', interactive: options.interactive });
|
|
171
223
|
let response;
|
|
172
224
|
activity.start('thinking');
|
|
173
225
|
try {
|
|
@@ -213,20 +265,40 @@ function formatUsd(value) {
|
|
|
213
265
|
return currency + (amount < 0.01 ? amount.toFixed(6) : amount.toFixed(4));
|
|
214
266
|
}
|
|
215
267
|
|
|
268
|
+
function formatElapsed(ms) {
|
|
269
|
+
const totalSeconds = Math.max(0, Math.round(Number(ms || 0) / 1000));
|
|
270
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
271
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
272
|
+
const seconds = totalSeconds % 60;
|
|
273
|
+
if (hours) return `${hours}h ${minutes}m ${seconds}s`;
|
|
274
|
+
if (minutes) return `${minutes}m ${seconds}s`;
|
|
275
|
+
return `${seconds}s`;
|
|
276
|
+
}
|
|
277
|
+
|
|
216
278
|
export function renderSessionReceipt(session) {
|
|
217
279
|
if (!session) return '';
|
|
218
280
|
const usage = session.cumulative_usage || {};
|
|
219
281
|
const input = Number(usage.input_tokens || 0);
|
|
220
282
|
const output = Number(usage.output_tokens || 0);
|
|
221
283
|
const cached = Number(usage.cached_input_tokens || 0);
|
|
284
|
+
const cacheWrite = Number(usage.cache_write_tokens || 0);
|
|
222
285
|
const reasoning = Number(usage.reasoning_tokens || 0);
|
|
223
286
|
const total = input + output;
|
|
224
287
|
const active = Number(session.usage_snapshot?.current_context?.input_tokens || 0);
|
|
288
|
+
const model = session.provider_model_id || session.model_key || 'model unavailable';
|
|
289
|
+
const breakdown = session.cost_breakdown_usd || {};
|
|
290
|
+
const componentTotal = ['input', 'cached_input', 'cache_write', 'output'].reduce((sum, key) => sum + Number(breakdown[key] || 0), 0);
|
|
291
|
+
const elapsed = localSessionElapsedMs(session);
|
|
225
292
|
const lines = [
|
|
226
293
|
'',
|
|
227
|
-
|
|
228
|
-
`
|
|
294
|
+
'Session summary',
|
|
295
|
+
`Token usage: total=${formatCount(total)} input=${formatCount(input)}${cached ? ` (+ ${formatCount(cached)} cached)` : ''}${cacheWrite ? ` (+ ${formatCount(cacheWrite)} cache write)` : ''} output=${formatCount(output)}${reasoning ? ` reasoning=${formatCount(reasoning)}` : ''}`,
|
|
296
|
+
`Spent: ${formatUsd(session.total_cost_usd)} · ${model}${session.actual_service_tier ? ` · ${session.actual_service_tier}` : ''}`,
|
|
297
|
+
`Elapsed: ${formatElapsed(elapsed)}`,
|
|
229
298
|
];
|
|
299
|
+
if (componentTotal > 0) {
|
|
300
|
+
lines.push(`Cost breakdown: input ${formatUsd(breakdown.input)} · cached ${formatUsd(breakdown.cached_input)} · cache write ${formatUsd(breakdown.cache_write)} · output ${formatUsd(breakdown.output)}`);
|
|
301
|
+
}
|
|
230
302
|
if (active) lines.push(`Active context: ${formatCount(active)} tokens`);
|
|
231
303
|
lines.push('', 'To continue this session, run:', ` agentsam resume ${session.id}`, '', 'Or run:', ' agentsam resume', '', 'and select:', ` ${session.title || 'this session'}`, '');
|
|
232
304
|
return lines.join('\n');
|
|
@@ -263,20 +335,28 @@ function safeToolInput(value, depth = 0) {
|
|
|
263
335
|
}
|
|
264
336
|
|
|
265
337
|
async function approveModelRequest(preflight, state) {
|
|
338
|
+
const projected = Number(preflight.projected_max_call_cost_usd);
|
|
339
|
+
const threshold = Number(process.env.AGENTSAM_CONFIRM_CALL_COST_USD || 0);
|
|
340
|
+
if (!(Number.isFinite(projected) && projected >= 0)) return true;
|
|
341
|
+
if (!(Number.isFinite(threshold) && threshold > 0) || projected <= threshold) return true;
|
|
342
|
+
|
|
266
343
|
const approvedCeiling = Number(state.session?.approved_projected_call_cost_usd || 0);
|
|
267
|
-
if (approvedCeiling > 0 &&
|
|
344
|
+
if (approvedCeiling > 0 && projected <= approvedCeiling) return true;
|
|
268
345
|
if (!state.interactive) return false;
|
|
346
|
+
|
|
347
|
+
state.activity?.clear?.();
|
|
269
348
|
writeLine(state.write, '');
|
|
270
|
-
writeLine(state.write, ' Model
|
|
271
|
-
writeLine(state.write, `
|
|
272
|
-
writeLine(state.write, `
|
|
273
|
-
writeLine(state.write, `
|
|
274
|
-
writeLine(state.write, `
|
|
275
|
-
writeLine(state.write, `
|
|
276
|
-
if (Number.isFinite(preflight.tokens_until_pricing_threshold)) writeLine(state.write, `
|
|
277
|
-
const approved = await confirm({ message: '
|
|
349
|
+
writeLine(state.write, ' ◆ Model cost approval');
|
|
350
|
+
writeLine(state.write, ` model ${preflight.model}`);
|
|
351
|
+
writeLine(state.write, ` reasoning ${preflight.reasoning_effort}`);
|
|
352
|
+
writeLine(state.write, ` processing ${preflight.service_tier}`);
|
|
353
|
+
writeLine(state.write, ` context ~${formatCount(preflight.estimated_input_tokens)} input tokens`);
|
|
354
|
+
writeLine(state.write, ` max call ${formatUsd(projected)} conservative ceiling`);
|
|
355
|
+
if (Number.isFinite(preflight.tokens_until_pricing_threshold)) writeLine(state.write, ` headroom ${formatCount(preflight.tokens_until_pricing_threshold)} tokens`);
|
|
356
|
+
const approved = await confirm({ message: 'Allow this call?', initialValue: false });
|
|
278
357
|
if (isCancel(approved) || approved !== true) return false;
|
|
279
|
-
persistSession(state, { approved_projected_call_cost_usd: Math.max(approvedCeiling,
|
|
358
|
+
persistSession(state, { approved_projected_call_cost_usd: Math.max(approvedCeiling, projected) });
|
|
359
|
+
state.activity?.start?.('Working');
|
|
280
360
|
return true;
|
|
281
361
|
}
|
|
282
362
|
|
|
@@ -288,6 +368,7 @@ async function approveToolExecution(request, state) {
|
|
|
288
368
|
if (!state.interactive) return false;
|
|
289
369
|
|
|
290
370
|
const summary = JSON.stringify(safeToolInput(request.input || {}));
|
|
371
|
+
state.activity?.clear?.();
|
|
291
372
|
writeLine(state.write, '');
|
|
292
373
|
writeLine(state.write, ' Agent Sam needs execution permission');
|
|
293
374
|
writeLine(state.write, ` action ${key}`);
|
|
@@ -306,26 +387,52 @@ async function approveToolExecution(request, state) {
|
|
|
306
387
|
});
|
|
307
388
|
if (isCancel(choice) || choice === 'deny') return false;
|
|
308
389
|
if (choice === 'always') grantExecutionApproval({ cwd: state.cwd, key, label: key }, { home: state.home });
|
|
390
|
+
state.activity?.start?.(`Working · ${request.capability_id}`);
|
|
309
391
|
return true;
|
|
310
392
|
}
|
|
311
393
|
|
|
312
394
|
async function runInteractiveModelTurn(prompt, state) {
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
395
|
+
const resolved = await resolveModelForTurn(state.cwd, state);
|
|
396
|
+
const { preferences, model, credential } = resolved;
|
|
397
|
+
const provider = createProviderAdapter({
|
|
398
|
+
modelRecord: model,
|
|
399
|
+
credential,
|
|
400
|
+
endpoint: model.provider === 'ollama' ? process.env.OLLAMA_BASE_URL : undefined,
|
|
401
|
+
fetchImpl: state.providerFetchImpl,
|
|
402
|
+
});
|
|
320
403
|
const capabilityAdapter = createCapabilityAdapter();
|
|
404
|
+
|
|
321
405
|
const samePolicy = state.session
|
|
322
406
|
&& state.session.model_key === model.model_key
|
|
323
407
|
&& state.session.reasoning_effort === preferences.reasoningEffort
|
|
324
408
|
&& state.session.requested_service_tier === preferences.serviceTier;
|
|
325
|
-
const
|
|
409
|
+
const previousProviderState = samePolicy ? state.session?.provider_state : null;
|
|
326
410
|
const previousUsageSnapshot = samePolicy ? state.session?.usage_snapshot : null;
|
|
411
|
+
const accountId = readAccountSession({ home: state.home })?.account_id || null;
|
|
412
|
+
let runtimeRunId = null;
|
|
413
|
+
try {
|
|
414
|
+
runtimeRunId = await startRuntimeRun({
|
|
415
|
+
cwd: state.cwd,
|
|
416
|
+
account_id: accountId,
|
|
417
|
+
mode: 'agent',
|
|
418
|
+
model_key: model.model_key,
|
|
419
|
+
reasoning_effort: preferences.reasoningEffort,
|
|
420
|
+
service_tier: preferences.serviceTier,
|
|
421
|
+
});
|
|
422
|
+
} catch {
|
|
423
|
+
runtimeRunId = null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const activity = createInlineActivity({
|
|
427
|
+
write: state.write,
|
|
428
|
+
interactive: state.interactive,
|
|
429
|
+
label: `Thinking · ${model.provider_model_id}`,
|
|
430
|
+
});
|
|
431
|
+
state.activity = activity;
|
|
432
|
+
const presenter = createCliRuntimePresenter({ activity, write: state.write, state });
|
|
433
|
+
const startedAt = Date.now();
|
|
434
|
+
activity.start(`Thinking · ${model.provider_model_id}`);
|
|
327
435
|
|
|
328
|
-
activity.start('thinking');
|
|
329
436
|
let result;
|
|
330
437
|
try {
|
|
331
438
|
result = await runResponsesAgent({
|
|
@@ -334,30 +441,80 @@ async function runInteractiveModelTurn(prompt, state) {
|
|
|
334
441
|
cwd: state.cwd,
|
|
335
442
|
prompt,
|
|
336
443
|
model: model.model_key,
|
|
444
|
+
modelRecord: model,
|
|
337
445
|
reasoningEffort: preferences.reasoningEffort,
|
|
338
446
|
serviceTier: preferences.serviceTier,
|
|
339
|
-
|
|
447
|
+
previousProviderState,
|
|
340
448
|
previousUsageSnapshot,
|
|
341
449
|
cumulativeUsage: state.session?.cumulative_usage || null,
|
|
342
450
|
promptCacheKey: state.session?.id || undefined,
|
|
343
|
-
runId: state.session?.id || undefined,
|
|
451
|
+
runId: runtimeRunId || state.session?.id || undefined,
|
|
344
452
|
beforeRequest: (preflight) => approveModelRequest(preflight, state),
|
|
345
453
|
beforeTool: (request) => approveToolExecution(request, state),
|
|
346
454
|
emit(event) {
|
|
455
|
+
presenter.handle(event);
|
|
347
456
|
if (event?.type === 'usage.snapshot') state.usageSnapshot = event.payload;
|
|
348
457
|
},
|
|
349
458
|
});
|
|
350
459
|
} catch (error) {
|
|
351
|
-
activity.fail('
|
|
460
|
+
activity.fail('Failed');
|
|
461
|
+
state.activity = null;
|
|
462
|
+
if (runtimeRunId) {
|
|
463
|
+
try {
|
|
464
|
+
await finishRuntimeRun({
|
|
465
|
+
cwd: state.cwd,
|
|
466
|
+
id: runtimeRunId,
|
|
467
|
+
status: 'failed',
|
|
468
|
+
error_code: error?.code || 'interactive_error',
|
|
469
|
+
error_message: error?.message || String(error),
|
|
470
|
+
latency_ms: Date.now() - startedAt,
|
|
471
|
+
});
|
|
472
|
+
} catch { /* execution result remains primary */ }
|
|
473
|
+
}
|
|
352
474
|
throw error;
|
|
353
475
|
}
|
|
354
|
-
|
|
476
|
+
|
|
477
|
+
activity.succeed(result.tool_receipts?.length ? `Done · ${result.tool_receipts.length} tool call${result.tool_receipts.length === 1 ? '' : 's'}` : 'Done');
|
|
478
|
+
state.activity = null;
|
|
479
|
+
|
|
480
|
+
const compacted = presenter.compactionReceipt();
|
|
481
|
+
if (compacted) writeLine(state.write, compacted);
|
|
482
|
+
|
|
355
483
|
if (result.output_text) {
|
|
356
484
|
writeLine(state.write, '');
|
|
357
485
|
writeLine(state.write, result.output_text);
|
|
358
486
|
writeLine(state.write, '');
|
|
359
487
|
}
|
|
488
|
+
|
|
360
489
|
state.usageSnapshot = result.usage_snapshot;
|
|
490
|
+
if (runtimeRunId) {
|
|
491
|
+
try {
|
|
492
|
+
await finishRuntimeRun({
|
|
493
|
+
cwd: state.cwd,
|
|
494
|
+
id: runtimeRunId,
|
|
495
|
+
status: 'completed',
|
|
496
|
+
actual_service_tier: result.actual_service_tier,
|
|
497
|
+
usage: result.cumulative_usage || result.usage_snapshot?.cumulative || {},
|
|
498
|
+
cost_usd: result.total_cost_usd || 0,
|
|
499
|
+
latency_ms: Date.now() - startedAt,
|
|
500
|
+
});
|
|
501
|
+
if (presenter.state.lastCompaction) {
|
|
502
|
+
await recordRuntimeCompaction({
|
|
503
|
+
cwd: state.cwd,
|
|
504
|
+
account_id: accountId,
|
|
505
|
+
agent_run_id: runtimeRunId,
|
|
506
|
+
session_id: state.session?.id,
|
|
507
|
+
provider: model.provider,
|
|
508
|
+
model_key: model.model_key,
|
|
509
|
+
tokens_before: presenter.state.lastCompaction.tokens_before,
|
|
510
|
+
tokens_after: presenter.state.lastCompaction.tokens_after,
|
|
511
|
+
summary_text: presenter.state.lastCompaction.summary_text || '',
|
|
512
|
+
source_kind: model.provider === 'ollama' ? 'filesystem' : 'api',
|
|
513
|
+
metadata: { verification: resolved.verification, compaction_id: presenter.state.lastCompaction.compaction_id || null },
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
} catch { /* local telemetry persistence is best-effort */ }
|
|
517
|
+
}
|
|
361
518
|
if (state.session) {
|
|
362
519
|
persistSession(state, {
|
|
363
520
|
status: 'active',
|
|
@@ -366,13 +523,24 @@ async function runInteractiveModelTurn(prompt, state) {
|
|
|
366
523
|
reasoning_effort: result.reasoning_effort,
|
|
367
524
|
requested_service_tier: result.requested_service_tier,
|
|
368
525
|
actual_service_tier: result.actual_service_tier,
|
|
369
|
-
provider_state: { provider: model.provider,
|
|
526
|
+
provider_state: { provider: model.provider, ...(result.provider_state || {}) },
|
|
370
527
|
usage_snapshot: result.usage_snapshot,
|
|
371
528
|
cumulative_usage: result.cumulative_usage,
|
|
372
529
|
total_cost_usd: Number(state.session.total_cost_usd || 0) + Number(result.total_cost_usd || 0),
|
|
530
|
+
cost_breakdown_usd: Object.fromEntries(['input', 'cached_input', 'cache_write', 'output'].map((key) => [
|
|
531
|
+
key, Number(state.session.cost_breakdown_usd?.[key] || 0) + Number(result.cost_breakdown_usd?.[key] || 0),
|
|
532
|
+
])),
|
|
373
533
|
last_error: null,
|
|
374
534
|
});
|
|
375
535
|
}
|
|
536
|
+
|
|
537
|
+
writeLine(state.write, renderCliFooter({
|
|
538
|
+
model: result.model || model.provider_model_id,
|
|
539
|
+
usageSnapshot: result.usage_snapshot,
|
|
540
|
+
tier: result.actual_service_tier,
|
|
541
|
+
elapsedMs: Date.now() - startedAt,
|
|
542
|
+
}));
|
|
543
|
+
writeLine(state.write, '');
|
|
376
544
|
return result;
|
|
377
545
|
}
|
|
378
546
|
|
|
@@ -390,13 +558,13 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
390
558
|
await showCommandPicker(state);
|
|
391
559
|
break;
|
|
392
560
|
case '/help':
|
|
393
|
-
write
|
|
561
|
+
await runHelp(args, { write, interactive: state.interactive });
|
|
394
562
|
break;
|
|
395
563
|
case '/exit':
|
|
396
564
|
case '/quit':
|
|
397
565
|
return { handled: true, exit: true, cwd: state.cwd };
|
|
398
566
|
case '/model': {
|
|
399
|
-
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, section: 'model' });
|
|
567
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, section: 'model', home: state.home });
|
|
400
568
|
state.cwd = configured.identity.root;
|
|
401
569
|
break;
|
|
402
570
|
}
|
|
@@ -425,10 +593,18 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
425
593
|
case '/models':
|
|
426
594
|
await runModels(args, { cwd: state.cwd, write, home: state.home });
|
|
427
595
|
break;
|
|
596
|
+
case '/login':
|
|
597
|
+
await runLogin(args, { write, home: state.home });
|
|
598
|
+
break;
|
|
599
|
+
case '/logout':
|
|
600
|
+
runLogout(args, { write, home: state.home });
|
|
601
|
+
if (state.session) write(renderSessionReceipt(state.session));
|
|
602
|
+
break;
|
|
428
603
|
case '/whoami':
|
|
429
604
|
await runWhoami(args, { write, home: state.home });
|
|
430
605
|
break;
|
|
431
606
|
case '/session':
|
|
607
|
+
case '/usage':
|
|
432
608
|
if (state.session) write(renderSessionReceipt(state.session));
|
|
433
609
|
else writeLine(write, ' No persistent session is active in this shell invocation.');
|
|
434
610
|
break;
|
|
@@ -437,7 +613,7 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
437
613
|
await runCloudflare(args.length ? args : ['commands'], { cwd: state.cwd, write });
|
|
438
614
|
break;
|
|
439
615
|
case '/settings': {
|
|
440
|
-
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
|
|
616
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, home: state.home });
|
|
441
617
|
state.cwd = configured.identity.root;
|
|
442
618
|
break;
|
|
443
619
|
}
|
|
@@ -502,6 +678,7 @@ export async function runShell(argv = [], options = {}) {
|
|
|
502
678
|
usageSnapshot: options.usageSnapshot || options.session?.usage_snapshot || null,
|
|
503
679
|
session: options.session || null,
|
|
504
680
|
home: options.home,
|
|
681
|
+
providerFetchImpl: options.providerFetchImpl,
|
|
505
682
|
};
|
|
506
683
|
const sub = argv[0] || '';
|
|
507
684
|
if (sub === 'list' || sub === 'status') { write(renderShellCatalog()); return; }
|
|
@@ -515,7 +692,12 @@ export async function runShell(argv = [], options = {}) {
|
|
|
515
692
|
|
|
516
693
|
if (state.session) {
|
|
517
694
|
state.cwd = path.resolve(state.session.cwd || state.cwd);
|
|
518
|
-
state.session = saveLocalSession({
|
|
695
|
+
state.session = saveLocalSession({
|
|
696
|
+
...state.session,
|
|
697
|
+
status: 'active',
|
|
698
|
+
cwd: state.cwd,
|
|
699
|
+
active_started_at: new Date().toISOString(),
|
|
700
|
+
}, { home: state.home });
|
|
519
701
|
state.usageSnapshot = state.session.usage_snapshot || state.usageSnapshot;
|
|
520
702
|
} else {
|
|
521
703
|
const preferences = readCliPreferences(state.cwd) || {};
|
|
@@ -551,7 +733,13 @@ export async function runShell(argv = [], options = {}) {
|
|
|
551
733
|
if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
|
|
552
734
|
}
|
|
553
735
|
if (state.session) {
|
|
554
|
-
|
|
736
|
+
const activeElapsedMs = localSessionElapsedMs(state.session);
|
|
737
|
+
persistSession(state, {
|
|
738
|
+
status: interrupted ? 'interrupted' : 'paused',
|
|
739
|
+
cwd: state.cwd,
|
|
740
|
+
active_elapsed_ms: activeElapsedMs,
|
|
741
|
+
active_started_at: null,
|
|
742
|
+
});
|
|
555
743
|
if (options.receipt !== false) write(renderSessionReceipt(state.session));
|
|
556
744
|
}
|
|
557
745
|
return state.session;
|