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