@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.0
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/AGENTSAM.md +55 -0
- package/README.md +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/RELEASES.md +16 -7
- package/package.json +20 -8
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/catalog.json +18 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -0
- package/src/cli.js +98 -28
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/deploy.js +39 -3
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/models.js +85 -40
- package/src/commands/preferences.js +101 -59
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +370 -109
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +55 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/cli-preferences.js +28 -24
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -16
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/process.js +35 -9
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +1 -0
- package/src/ui/boot.js +15 -17
- package/test/account-session.test.mjs +36 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +30 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +4 -4
- package/test/responses-runner.test.mjs +148 -0
- package/test/shell.test.mjs +47 -20
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
package/src/commands/shell.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { confirm, isCancel, select } from '@clack/prompts';
|
|
6
7
|
import { SLASH_COMMANDS, SHELL_PHASES } from '../lib/slash-commands.js';
|
|
7
8
|
import { runContext } from './context.js';
|
|
8
9
|
import { runDb } from './db.js';
|
|
@@ -10,11 +11,20 @@ import { runDeploy } from './deploy.js';
|
|
|
10
11
|
import { runStatus } from './status.js';
|
|
11
12
|
import { runModels } from './models.js';
|
|
12
13
|
import { configureCliPreferences } from './preferences.js';
|
|
14
|
+
import { runCloudflare } from './cloudflare.js';
|
|
13
15
|
import { createRuntimeActivity } from '../ui/runtime-activity.js';
|
|
16
|
+
import { diagnosticFromError, renderDiagnosticError } from '../errors/index.js';
|
|
17
|
+
import { getModelRecord } from '../models/index.js';
|
|
18
|
+
import { readCliPreferences, updateCliPreferences } from '../lib/cli-preferences.js';
|
|
19
|
+
import { buildContextEconomicsReport, renderContextEconomics } from './context-economics.js';
|
|
20
|
+
import { createOpenAIResponsesAdapter } from '../providers/index.js';
|
|
21
|
+
import { createCapabilityAdapter, runResponsesAgent } from '../agent/index.js';
|
|
22
|
+
import { resolveProviderCredential } from '../lib/provider-credentials.js';
|
|
23
|
+
import { createLocalSession, saveLocalSession, sessionTitleFromInput } from '../lib/local-sessions.js';
|
|
24
|
+
import { grantExecutionApproval, isExecutionApproved, toolApprovalKey } from '../lib/execution-approvals.js';
|
|
25
|
+
import { runWhoami } from './whoami.js';
|
|
14
26
|
|
|
15
|
-
function writeLine(write, value = '') {
|
|
16
|
-
write(`${value}\n`);
|
|
17
|
-
}
|
|
27
|
+
function writeLine(write, value = '') { write(`${value}\n`); }
|
|
18
28
|
|
|
19
29
|
export function compactCwd(value, home = process.env.HOME || process.env.USERPROFILE || '') {
|
|
20
30
|
const cwd = path.resolve(value);
|
|
@@ -41,34 +51,22 @@ export function tokenizeShellLine(input = '') {
|
|
|
41
51
|
let token = '';
|
|
42
52
|
let quote = '';
|
|
43
53
|
let started = false;
|
|
44
|
-
|
|
45
54
|
const flush = () => {
|
|
46
55
|
if (!started) return;
|
|
47
56
|
tokens.push(token);
|
|
48
57
|
token = '';
|
|
49
58
|
started = false;
|
|
50
59
|
};
|
|
51
|
-
|
|
52
60
|
for (let i = 0; i < source.length; i += 1) {
|
|
53
61
|
const ch = source[i];
|
|
54
62
|
if (quote) {
|
|
55
|
-
if (ch === quote)
|
|
56
|
-
|
|
57
|
-
} else {
|
|
58
|
-
token += ch;
|
|
59
|
-
}
|
|
63
|
+
if (ch === quote) quote = '';
|
|
64
|
+
else token += ch;
|
|
60
65
|
started = true;
|
|
61
66
|
continue;
|
|
62
67
|
}
|
|
63
|
-
if (ch === '"' || ch === "'") {
|
|
64
|
-
|
|
65
|
-
started = true;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (/\s/.test(ch)) {
|
|
69
|
-
flush();
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
68
|
+
if (ch === '"' || ch === "'") { quote = ch; started = true; continue; }
|
|
69
|
+
if (/\s/.test(ch)) { flush(); continue; }
|
|
72
70
|
token += ch;
|
|
73
71
|
started = true;
|
|
74
72
|
}
|
|
@@ -84,13 +82,10 @@ export function renderShellCatalog() {
|
|
|
84
82
|
║ Agent Sam Terminal ║
|
|
85
83
|
╚════════════════════════════════╝
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
DB agentsam db status local SQLite
|
|
90
|
-
|
|
91
|
-
Current milestone: ${next?.label ?? 'local terminal experience'}
|
|
85
|
+
Current milestone: ${next?.label ?? 'interactive runtime'}
|
|
86
|
+
Type / and press Enter for the scrollable command picker.
|
|
92
87
|
|
|
93
|
-
Slash commands (${SLASH_COMMANDS.length}
|
|
88
|
+
Slash commands (${SLASH_COMMANDS.length} implemented):
|
|
94
89
|
${rows}
|
|
95
90
|
`;
|
|
96
91
|
}
|
|
@@ -106,6 +101,63 @@ function parseDeployOptions(args, cwd) {
|
|
|
106
101
|
return opts;
|
|
107
102
|
}
|
|
108
103
|
|
|
104
|
+
function spawnGit(cwd, args) {
|
|
105
|
+
const result = spawnSync('git', args, { cwd, stdio: 'inherit', shell: false });
|
|
106
|
+
if (result.error) throw result.error;
|
|
107
|
+
if (result.status !== 0) throw new Error(`git exited ${result.status}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function selectedModel(cwd) {
|
|
111
|
+
const preferences = readCliPreferences(cwd) || {};
|
|
112
|
+
const model = getModelRecord(preferences.modelPreference);
|
|
113
|
+
if (!model) throw new Error('Select an exact provider-verified model with /model first so Agent Sam can verify supported runtime controls.');
|
|
114
|
+
return { preferences, model };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function chooseReasoning(cwd, args, state) {
|
|
118
|
+
const { preferences, model } = selectedModel(cwd);
|
|
119
|
+
let effort = String(args[0] || '').trim().toLowerCase();
|
|
120
|
+
if (!effort) {
|
|
121
|
+
if (!state.interactive) {
|
|
122
|
+
writeLine(state.write, ` ${model.provider_model_id} reasoning: ${model.reasoning_efforts.join(' | ')}`);
|
|
123
|
+
writeLine(state.write, ` current: ${preferences.reasoningEffort || 'auto'}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const choice = await select({
|
|
127
|
+
message: `Reasoning level · ${model.provider_model_id}`,
|
|
128
|
+
initialValue: model.reasoning_efforts.includes(preferences.reasoningEffort) ? preferences.reasoningEffort : model.reasoning_efforts[0],
|
|
129
|
+
options: model.reasoning_efforts.map((value) => ({ value, label: value === 'xhigh' ? 'Extra high' : value === 'max' ? 'Max' : value[0].toUpperCase() + value.slice(1) })),
|
|
130
|
+
});
|
|
131
|
+
if (isCancel(choice)) return;
|
|
132
|
+
effort = choice;
|
|
133
|
+
}
|
|
134
|
+
if (!model.reasoning_efforts.includes(effort)) throw new Error(`unsupported reasoning level for ${model.provider_model_id}: ${effort}`);
|
|
135
|
+
updateCliPreferences(cwd, { reasoningEffort: effort });
|
|
136
|
+
writeLine(state.write, ` reasoning → ${effort}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setServiceTier(cwd, tier, write) {
|
|
140
|
+
const { model } = selectedModel(cwd);
|
|
141
|
+
if (!model.service_tiers.includes(tier)) throw new Error(`${model.provider_model_id} does not declare ${tier} processing support`);
|
|
142
|
+
updateCliPreferences(cwd, { serviceTier: tier });
|
|
143
|
+
const label = tier === 'default' ? 'standard' : tier;
|
|
144
|
+
writeLine(write, ` processing → ${label}`);
|
|
145
|
+
if (tier === 'fast') writeLine(write, ' Fast is a paid latency choice; Agent Sam will account for its model-specific pricing multiplier.');
|
|
146
|
+
if (tier === 'flex') writeLine(write, ' Flex trades latency/capacity availability for lower cost; it is not Batch.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function showCommandPicker(state) {
|
|
150
|
+
if (!state.interactive) {
|
|
151
|
+
state.write(renderShellCatalog());
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const choice = await select({
|
|
155
|
+
message: 'Agent Sam commands',
|
|
156
|
+
options: SLASH_COMMANDS.map((row) => ({ value: row.cmd, label: row.cmd, hint: row.description })),
|
|
157
|
+
});
|
|
158
|
+
if (isCancel(choice)) return;
|
|
159
|
+
await dispatchShellLine(choice, state);
|
|
160
|
+
}
|
|
109
161
|
|
|
110
162
|
export async function runLocalAgent(goal, write, options = {}) {
|
|
111
163
|
if (!goal) {
|
|
@@ -115,90 +167,280 @@ export async function runLocalAgent(goal, write, options = {}) {
|
|
|
115
167
|
}
|
|
116
168
|
const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
117
169
|
const fetchImpl = options.fetchImpl || fetch;
|
|
118
|
-
const activity = options.activity || createRuntimeActivity({
|
|
119
|
-
write,
|
|
120
|
-
phase: 'thinking',
|
|
121
|
-
interactive: options.interactive,
|
|
122
|
-
});
|
|
170
|
+
const activity = options.activity || createRuntimeActivity({ write, phase: 'thinking', interactive: options.interactive });
|
|
123
171
|
let response;
|
|
124
172
|
activity.start('thinking');
|
|
125
173
|
try {
|
|
126
174
|
response = await fetchImpl(`${base}/api/agentsam/message`, {
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: { 'content-type': 'application/json' },
|
|
129
|
-
body: JSON.stringify({ message: goal }),
|
|
175
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message: goal }),
|
|
130
176
|
});
|
|
131
177
|
} catch (error) {
|
|
132
178
|
activity.fail('unavailable');
|
|
133
179
|
throw new Error(`local Agent Sam unavailable at ${base} — run \`npm run dev\` first (${error?.message || error})`);
|
|
134
180
|
}
|
|
135
181
|
let text;
|
|
136
|
-
try {
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
activity.fail('response error');
|
|
140
|
-
throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`);
|
|
141
|
-
}
|
|
142
|
-
if (!response.ok) {
|
|
143
|
-
activity.fail(`HTTP ${response.status}`);
|
|
144
|
-
throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`);
|
|
145
|
-
}
|
|
182
|
+
try { text = await response.text(); }
|
|
183
|
+
catch (error) { activity.fail('response error'); throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`); }
|
|
184
|
+
if (!response.ok) { activity.fail(`HTTP ${response.status}`); throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`); }
|
|
146
185
|
activity.succeed('done');
|
|
147
|
-
try {
|
|
148
|
-
|
|
149
|
-
} catch {
|
|
150
|
-
writeLine(write, text);
|
|
151
|
-
}
|
|
186
|
+
try { writeLine(write, JSON.stringify(JSON.parse(text), null, 2)); }
|
|
187
|
+
catch { writeLine(write, text); }
|
|
152
188
|
}
|
|
153
189
|
|
|
154
190
|
async function showLocalLogs(cwd, write) {
|
|
155
191
|
const dbPath = path.join(cwd, '.agentsam', 'data', 'agentsam.sqlite');
|
|
156
|
-
if (!fs.existsSync(dbPath)) {
|
|
157
|
-
writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.');
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
192
|
+
if (!fs.existsSync(dbPath)) { writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.'); return; }
|
|
160
193
|
const { createLocalSqliteDatabase } = await import('../local/sqlite.js');
|
|
161
194
|
const db = await createLocalSqliteDatabase(dbPath);
|
|
162
195
|
try {
|
|
163
|
-
const calls = await db
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
writeLine(write, ' No local Agent Sam tool-call events yet.');
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
writeLine(write, '');
|
|
171
|
-
writeLine(write, ' Recent Agent Sam tool calls');
|
|
172
|
-
for (const row of calls.results) {
|
|
173
|
-
writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
|
|
174
|
-
}
|
|
196
|
+
const calls = await db.prepare('SELECT id, session_id, tool_name, status, created_at, completed_at FROM agent_tool_calls ORDER BY created_at DESC LIMIT 20').all();
|
|
197
|
+
if (!calls.results.length) { writeLine(write, ' No local Agent Sam tool-call events yet.'); return; }
|
|
198
|
+
writeLine(write, '\n Recent Agent Sam tool calls');
|
|
199
|
+
for (const row of calls.results) writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
|
|
175
200
|
writeLine(write, '');
|
|
176
|
-
} finally {
|
|
177
|
-
|
|
201
|
+
} finally { db.close(); }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatCount(value) {
|
|
205
|
+
return Math.max(0, Number(value || 0)).toLocaleString('en-US');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function formatUsd(value) {
|
|
209
|
+
const amount = Number(value || 0);
|
|
210
|
+
if (!Number.isFinite(amount)) return 'unavailable';
|
|
211
|
+
const currency = String.fromCharCode(36);
|
|
212
|
+
if (amount === 0) return currency + '0.000000';
|
|
213
|
+
return currency + (amount < 0.01 ? amount.toFixed(6) : amount.toFixed(4));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function renderSessionReceipt(session) {
|
|
217
|
+
if (!session) return '';
|
|
218
|
+
const usage = session.cumulative_usage || {};
|
|
219
|
+
const input = Number(usage.input_tokens || 0);
|
|
220
|
+
const output = Number(usage.output_tokens || 0);
|
|
221
|
+
const cached = Number(usage.cached_input_tokens || 0);
|
|
222
|
+
const reasoning = Number(usage.reasoning_tokens || 0);
|
|
223
|
+
const total = input + output;
|
|
224
|
+
const active = Number(session.usage_snapshot?.current_context?.input_tokens || 0);
|
|
225
|
+
const lines = [
|
|
226
|
+
'',
|
|
227
|
+
`Token usage: total=${formatCount(total)} input=${formatCount(input)}${cached ? ` (+ ${formatCount(cached)} cached)` : ''} output=${formatCount(output)}${reasoning ? ` reasoning=${formatCount(reasoning)}` : ''}`,
|
|
228
|
+
`Cost: ${formatUsd(session.total_cost_usd)} · ${session.model_key || 'model unavailable'}${session.actual_service_tier ? ` · ${session.actual_service_tier}` : ''}`,
|
|
229
|
+
];
|
|
230
|
+
if (active) lines.push(`Active context: ${formatCount(active)} tokens`);
|
|
231
|
+
lines.push('', 'To continue this session, run:', ` agentsam resume ${session.id}`, '', 'Or run:', ' agentsam resume', '', 'and select:', ` ${session.title || 'this session'}`, '');
|
|
232
|
+
return lines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function persistSession(state, patch = {}) {
|
|
236
|
+
if (!state.session) return null;
|
|
237
|
+
state.session = saveLocalSession({ ...state.session, ...patch }, { home: state.home });
|
|
238
|
+
return state.session;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function recordSessionInput(state, input) {
|
|
242
|
+
const value = String(input || '').trim();
|
|
243
|
+
if (!state.session || !value) return;
|
|
244
|
+
const housekeeping = new Set(['/exit', '/quit', '/session', '/help', '/', '/menu', '/clear']);
|
|
245
|
+
if (housekeeping.has(value.toLowerCase())) return;
|
|
246
|
+
persistSession(state, {
|
|
247
|
+
status: 'active',
|
|
248
|
+
last_input: value,
|
|
249
|
+
title: sessionTitleFromInput(value),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function safeToolInput(value, depth = 0) {
|
|
254
|
+
if (depth > 3) return '[nested]';
|
|
255
|
+
if (Array.isArray(value)) return value.slice(0, 12).map((row) => safeToolInput(row, depth + 1));
|
|
256
|
+
if (!value || typeof value !== 'object') return value;
|
|
257
|
+
const output = {};
|
|
258
|
+
for (const [key, item] of Object.entries(value)) {
|
|
259
|
+
if (/token|secret|password|api[_-]?key|authorization/i.test(key)) output[key] = '[REDACTED]';
|
|
260
|
+
else output[key] = safeToolInput(item, depth + 1);
|
|
261
|
+
}
|
|
262
|
+
return output;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function approveModelRequest(preflight, state) {
|
|
266
|
+
const approvedCeiling = Number(state.session?.approved_projected_call_cost_usd || 0);
|
|
267
|
+
if (approvedCeiling > 0 && preflight.projected_max_call_cost_usd <= approvedCeiling) return true;
|
|
268
|
+
if (!state.interactive) return false;
|
|
269
|
+
writeLine(state.write, '');
|
|
270
|
+
writeLine(state.write, ' Model request');
|
|
271
|
+
writeLine(state.write, ` model ${preflight.model}`);
|
|
272
|
+
writeLine(state.write, ` reasoning ${preflight.reasoning_effort}`);
|
|
273
|
+
writeLine(state.write, ` processing ${preflight.service_tier}`);
|
|
274
|
+
writeLine(state.write, ` context ~${formatCount(preflight.estimated_input_tokens)} input tokens`);
|
|
275
|
+
writeLine(state.write, ` max call ${formatUsd(preflight.projected_max_call_cost_usd)} conservative ceiling`);
|
|
276
|
+
if (Number.isFinite(preflight.tokens_until_pricing_threshold)) writeLine(state.write, ` price cliff ${formatCount(preflight.tokens_until_pricing_threshold)} tokens headroom`);
|
|
277
|
+
const approved = await confirm({ message: 'Send this request?', initialValue: true });
|
|
278
|
+
if (isCancel(approved) || approved !== true) return false;
|
|
279
|
+
persistSession(state, { approved_projected_call_cost_usd: Math.max(approvedCeiling, preflight.projected_max_call_cost_usd) });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function approveToolExecution(request, state) {
|
|
284
|
+
const sideEffects = request?.descriptor?.side_effects || 'none';
|
|
285
|
+
if (sideEffects === 'none') return true;
|
|
286
|
+
const key = toolApprovalKey(request.capability_id, request.input);
|
|
287
|
+
if (isExecutionApproved({ cwd: state.cwd, key }, { home: state.home })) return true;
|
|
288
|
+
if (!state.interactive) return false;
|
|
289
|
+
|
|
290
|
+
const summary = JSON.stringify(safeToolInput(request.input || {}));
|
|
291
|
+
writeLine(state.write, '');
|
|
292
|
+
writeLine(state.write, ' Agent Sam needs execution permission');
|
|
293
|
+
writeLine(state.write, ` action ${key}`);
|
|
294
|
+
writeLine(state.write, ` target local runtime · ${compactCwd(state.cwd)}`);
|
|
295
|
+
writeLine(state.write, ` effect ${sideEffects}`);
|
|
296
|
+
if (summary && summary !== '{}') writeLine(state.write, ` input ${summary.length > 500 ? `${summary.slice(0, 497)}...` : summary}`);
|
|
297
|
+
writeLine(state.write, ' secrets remain runtime-owned and are not included in the model-visible result.');
|
|
298
|
+
|
|
299
|
+
const choice = await select({
|
|
300
|
+
message: `Allow ${key}?`,
|
|
301
|
+
options: [
|
|
302
|
+
{ value: 'once', label: 'Allow once' },
|
|
303
|
+
{ value: 'always', label: `Always allow ${key} in this project` },
|
|
304
|
+
{ value: 'deny', label: 'Deny' },
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
if (isCancel(choice) || choice === 'deny') return false;
|
|
308
|
+
if (choice === 'always') grantExecutionApproval({ cwd: state.cwd, key, label: key }, { home: state.home });
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function runInteractiveModelTurn(prompt, state) {
|
|
313
|
+
const { preferences, model } = selectedModel(state.cwd);
|
|
314
|
+
if (model.provider !== 'openai') throw new Error(`interactive_provider_not_implemented:${model.provider}`);
|
|
315
|
+
const credential = resolveProviderCredential(model.provider, { home: state.home });
|
|
316
|
+
if (!credential.configured) throw new Error(`provider_credential_unavailable:${model.provider}:${credential.error || credential.env || 'not_configured'}`);
|
|
317
|
+
|
|
318
|
+
const activity = createRuntimeActivity({ write: state.write, phase: 'thinking', interactive: state.interactive });
|
|
319
|
+
const provider = createOpenAIResponsesAdapter({ apiKey: credential.value });
|
|
320
|
+
const capabilityAdapter = createCapabilityAdapter();
|
|
321
|
+
const samePolicy = state.session
|
|
322
|
+
&& state.session.model_key === model.model_key
|
|
323
|
+
&& state.session.reasoning_effort === preferences.reasoningEffort
|
|
324
|
+
&& state.session.requested_service_tier === preferences.serviceTier;
|
|
325
|
+
const previousResponseId = samePolicy ? state.session?.provider_state?.previous_response_id : null;
|
|
326
|
+
const previousUsageSnapshot = samePolicy ? state.session?.usage_snapshot : null;
|
|
327
|
+
|
|
328
|
+
activity.start('thinking');
|
|
329
|
+
let result;
|
|
330
|
+
try {
|
|
331
|
+
result = await runResponsesAgent({
|
|
332
|
+
provider,
|
|
333
|
+
capabilityAdapter,
|
|
334
|
+
cwd: state.cwd,
|
|
335
|
+
prompt,
|
|
336
|
+
model: model.model_key,
|
|
337
|
+
reasoningEffort: preferences.reasoningEffort,
|
|
338
|
+
serviceTier: preferences.serviceTier,
|
|
339
|
+
previousResponseId,
|
|
340
|
+
previousUsageSnapshot,
|
|
341
|
+
cumulativeUsage: state.session?.cumulative_usage || null,
|
|
342
|
+
promptCacheKey: state.session?.id || undefined,
|
|
343
|
+
runId: state.session?.id || undefined,
|
|
344
|
+
beforeRequest: (preflight) => approveModelRequest(preflight, state),
|
|
345
|
+
beforeTool: (request) => approveToolExecution(request, state),
|
|
346
|
+
emit(event) {
|
|
347
|
+
if (event?.type === 'usage.snapshot') state.usageSnapshot = event.payload;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
} catch (error) {
|
|
351
|
+
activity.fail('failed');
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
activity.succeed('done');
|
|
355
|
+
if (result.output_text) {
|
|
356
|
+
writeLine(state.write, '');
|
|
357
|
+
writeLine(state.write, result.output_text);
|
|
358
|
+
writeLine(state.write, '');
|
|
359
|
+
}
|
|
360
|
+
state.usageSnapshot = result.usage_snapshot;
|
|
361
|
+
if (state.session) {
|
|
362
|
+
persistSession(state, {
|
|
363
|
+
status: 'active',
|
|
364
|
+
model_key: model.model_key,
|
|
365
|
+
provider_model_id: result.model,
|
|
366
|
+
reasoning_effort: result.reasoning_effort,
|
|
367
|
+
requested_service_tier: result.requested_service_tier,
|
|
368
|
+
actual_service_tier: result.actual_service_tier,
|
|
369
|
+
provider_state: { provider: model.provider, previous_response_id: result.response_id },
|
|
370
|
+
usage_snapshot: result.usage_snapshot,
|
|
371
|
+
cumulative_usage: result.cumulative_usage,
|
|
372
|
+
total_cost_usd: Number(state.session.total_cost_usd || 0) + Number(result.total_cost_usd || 0),
|
|
373
|
+
last_error: null,
|
|
374
|
+
});
|
|
178
375
|
}
|
|
376
|
+
return result;
|
|
179
377
|
}
|
|
180
378
|
|
|
181
379
|
export async function dispatchShellLine(line, state = {}) {
|
|
182
380
|
const tokens = tokenizeShellLine(line);
|
|
183
381
|
const write = state.write || ((text) => process.stdout.write(text));
|
|
382
|
+
state.write = write;
|
|
184
383
|
state.cwd = path.resolve(state.cwd || process.cwd());
|
|
185
384
|
if (!tokens.length) return { handled: true, exit: false, cwd: state.cwd };
|
|
186
|
-
|
|
187
385
|
const [command, ...args] = tokens;
|
|
188
386
|
try {
|
|
189
387
|
switch (command.toLowerCase()) {
|
|
388
|
+
case '/':
|
|
389
|
+
case '/menu':
|
|
390
|
+
await showCommandPicker(state);
|
|
391
|
+
break;
|
|
190
392
|
case '/help':
|
|
191
393
|
write(renderShellCatalog());
|
|
192
|
-
|
|
394
|
+
break;
|
|
193
395
|
case '/exit':
|
|
194
396
|
case '/quit':
|
|
195
397
|
return { handled: true, exit: true, cwd: state.cwd };
|
|
398
|
+
case '/model': {
|
|
399
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, section: 'model' });
|
|
400
|
+
state.cwd = configured.identity.root;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case '/reasoning':
|
|
404
|
+
await chooseReasoning(state.cwd, args, state);
|
|
405
|
+
break;
|
|
406
|
+
case '/fast':
|
|
407
|
+
setServiceTier(state.cwd, 'fast', write);
|
|
408
|
+
break;
|
|
409
|
+
case '/flex':
|
|
410
|
+
setServiceTier(state.cwd, 'flex', write);
|
|
411
|
+
break;
|
|
412
|
+
case '/standard':
|
|
413
|
+
setServiceTier(state.cwd, 'default', write);
|
|
414
|
+
break;
|
|
415
|
+
case '/context':
|
|
416
|
+
if (args[0] === 'repo' || args[0] === 'git') await runContext(['--cwd', state.cwd, ...args.slice(1)]);
|
|
417
|
+
else write(renderContextEconomics(buildContextEconomicsReport(state.cwd, {
|
|
418
|
+
activeInputTokens: state.usageSnapshot?.current_context?.input_tokens,
|
|
419
|
+
estimateKind: state.usageSnapshot?.estimate_kind,
|
|
420
|
+
})));
|
|
421
|
+
break;
|
|
196
422
|
case '/status':
|
|
197
423
|
await runStatus(args, { cwd: state.cwd });
|
|
198
424
|
break;
|
|
199
|
-
case '/
|
|
200
|
-
await
|
|
425
|
+
case '/models':
|
|
426
|
+
await runModels(args, { cwd: state.cwd, write, home: state.home });
|
|
427
|
+
break;
|
|
428
|
+
case '/whoami':
|
|
429
|
+
await runWhoami(args, { write, home: state.home });
|
|
430
|
+
break;
|
|
431
|
+
case '/session':
|
|
432
|
+
if (state.session) write(renderSessionReceipt(state.session));
|
|
433
|
+
else writeLine(write, ' No persistent session is active in this shell invocation.');
|
|
434
|
+
break;
|
|
435
|
+
case '/cf':
|
|
436
|
+
case '/cloudflare':
|
|
437
|
+
await runCloudflare(args.length ? args : ['commands'], { cwd: state.cwd, write });
|
|
438
|
+
break;
|
|
439
|
+
case '/settings': {
|
|
440
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
|
|
441
|
+
state.cwd = configured.identity.root;
|
|
201
442
|
break;
|
|
443
|
+
}
|
|
202
444
|
case '/pwd':
|
|
203
445
|
writeLine(write, state.cwd);
|
|
204
446
|
break;
|
|
@@ -210,54 +452,59 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
210
452
|
writeLine(write, state.cwd);
|
|
211
453
|
break;
|
|
212
454
|
}
|
|
213
|
-
case '/git':
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
455
|
+
case '/git':
|
|
456
|
+
spawnGit(state.cwd, args.length ? args : ['status', '--short', '--branch']);
|
|
457
|
+
break;
|
|
458
|
+
case '/diff':
|
|
459
|
+
spawnGit(state.cwd, ['diff', ...args]);
|
|
218
460
|
break;
|
|
219
|
-
}
|
|
220
461
|
case '/db':
|
|
221
462
|
await runDb(args.length ? args : ['status'], { cwd: state.cwd });
|
|
222
463
|
break;
|
|
223
464
|
case '/agent':
|
|
224
465
|
await runLocalAgent(args.join(' '), write, { interactive: state.interactive });
|
|
225
466
|
break;
|
|
226
|
-
case '/models':
|
|
227
|
-
await runModels(args, { cwd: state.cwd, write });
|
|
228
|
-
break;
|
|
229
|
-
case '/settings': {
|
|
230
|
-
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
|
|
231
|
-
state.cwd = configured.identity.root;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
467
|
case '/logs':
|
|
235
468
|
await showLocalLogs(state.cwd, write);
|
|
236
469
|
break;
|
|
237
470
|
case '/deploy':
|
|
238
471
|
await runDeploy(parseDeployOptions(args, state.cwd));
|
|
239
472
|
break;
|
|
473
|
+
case '/clear':
|
|
474
|
+
write('\x1b[2J\x1b[H');
|
|
475
|
+
break;
|
|
240
476
|
default:
|
|
477
|
+
if (!command.startsWith('/')) {
|
|
478
|
+
await runInteractiveModelTurn(line, state);
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
241
481
|
writeLine(write, ` Unknown Agent Sam command: ${command}`);
|
|
242
|
-
writeLine(write, ' Type /help for available commands.');
|
|
482
|
+
writeLine(write, ' Type / or /help for available commands.');
|
|
243
483
|
return { handled: false, exit: false, cwd: state.cwd };
|
|
244
484
|
}
|
|
245
485
|
} catch (error) {
|
|
246
|
-
|
|
486
|
+
if (state.session) {
|
|
487
|
+
persistSession(state, { last_error: diagnosticFromError(error, { source: 'shell', kind: 'interactive_error' }) });
|
|
488
|
+
}
|
|
489
|
+
if (!error?.reported) {
|
|
490
|
+
for (const line of renderDiagnosticError(error).split('\n')) writeLine(write, ` ${line}`);
|
|
491
|
+
}
|
|
247
492
|
}
|
|
248
|
-
|
|
249
493
|
return { handled: true, exit: false, cwd: state.cwd };
|
|
250
494
|
}
|
|
251
495
|
|
|
252
496
|
export async function runShell(argv = [], options = {}) {
|
|
253
497
|
const write = options.write || ((text) => process.stdout.write(text));
|
|
254
|
-
const state = {
|
|
498
|
+
const state = {
|
|
499
|
+
cwd: path.resolve(options.cwd || process.cwd()),
|
|
500
|
+
write,
|
|
501
|
+
interactive: options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
502
|
+
usageSnapshot: options.usageSnapshot || options.session?.usage_snapshot || null,
|
|
503
|
+
session: options.session || null,
|
|
504
|
+
home: options.home,
|
|
505
|
+
};
|
|
255
506
|
const sub = argv[0] || '';
|
|
256
|
-
|
|
257
|
-
if (sub === 'list' || sub === 'status') {
|
|
258
|
-
write(renderShellCatalog());
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
507
|
+
if (sub === 'list' || sub === 'status') { write(renderShellCatalog()); return; }
|
|
261
508
|
if (sub === '--command' || sub === '--once') {
|
|
262
509
|
const line = argv.slice(1).join(' ');
|
|
263
510
|
if (!line) throw new Error(`${sub} requires a slash command`);
|
|
@@ -266,32 +513,46 @@ export async function runShell(argv = [], options = {}) {
|
|
|
266
513
|
}
|
|
267
514
|
if (sub) throw new Error(`unknown shell option: ${sub}`);
|
|
268
515
|
|
|
516
|
+
if (state.session) {
|
|
517
|
+
state.cwd = path.resolve(state.session.cwd || state.cwd);
|
|
518
|
+
state.session = saveLocalSession({ ...state.session, status: 'active', cwd: state.cwd }, { home: state.home });
|
|
519
|
+
state.usageSnapshot = state.session.usage_snapshot || state.usageSnapshot;
|
|
520
|
+
} else {
|
|
521
|
+
const preferences = readCliPreferences(state.cwd) || {};
|
|
522
|
+
state.session = createLocalSession({
|
|
523
|
+
cwd: state.cwd,
|
|
524
|
+
status: 'active',
|
|
525
|
+
model_key: preferences.modelPreference !== 'auto' ? preferences.modelPreference : null,
|
|
526
|
+
reasoning_effort: preferences.reasoningEffort !== 'auto' ? preferences.reasoningEffort : null,
|
|
527
|
+
requested_service_tier: preferences.serviceTier || 'default',
|
|
528
|
+
}, { home: state.home });
|
|
529
|
+
}
|
|
530
|
+
|
|
269
531
|
if (options.intro !== 'quiet') {
|
|
270
532
|
write(renderShellCatalog());
|
|
271
|
-
writeLine(write, ' Interactive shell ready. Type /
|
|
272
|
-
writeLine(write, '');
|
|
533
|
+
writeLine(write, ' Interactive shell ready. Type / for the command picker; /exit to return to your host shell.\n');
|
|
273
534
|
}
|
|
274
|
-
|
|
275
535
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
|
|
536
|
+
let interrupted = false;
|
|
537
|
+
rl.on('SIGINT', () => {
|
|
538
|
+
interrupted = true;
|
|
539
|
+
rl.close();
|
|
540
|
+
});
|
|
276
541
|
const promptText = () => {
|
|
277
542
|
if (typeof options.prompt === 'function') return options.prompt(state);
|
|
278
543
|
if (typeof options.prompt === 'string' && options.prompt) return options.prompt;
|
|
279
544
|
return renderShellPrompt(state.cwd);
|
|
280
545
|
};
|
|
281
|
-
if (rl.terminal) {
|
|
282
|
-
rl.setPrompt(promptText());
|
|
283
|
-
rl.prompt();
|
|
284
|
-
}
|
|
285
|
-
|
|
546
|
+
if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
|
|
286
547
|
for await (const line of rl) {
|
|
548
|
+
recordSessionInput(state, line);
|
|
287
549
|
const result = await dispatchShellLine(line, state);
|
|
288
|
-
if (result.exit) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
rl.prompt();
|
|
295
|
-
}
|
|
550
|
+
if (result.exit) { rl.close(); break; }
|
|
551
|
+
if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
|
|
552
|
+
}
|
|
553
|
+
if (state.session) {
|
|
554
|
+
persistSession(state, { status: interrupted ? 'interrupted' : 'paused', cwd: state.cwd });
|
|
555
|
+
if (options.receipt !== false) write(renderSessionReceipt(state.session));
|
|
296
556
|
}
|
|
557
|
+
return state.session;
|
|
297
558
|
}
|
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 { resolveAccountSdkKey } from '../lib/account-session.js';
|
|
11
11
|
|
|
12
12
|
const DEFAULT_PORT = 3099;
|
|
13
13
|
|
|
@@ -51,7 +51,7 @@ function ensureCloudflared() {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
async function resolveToken() {
|
|
54
|
-
const existing =
|
|
54
|
+
const existing = resolveAccountSdkKey({ env: process.env }).value;
|
|
55
55
|
if (existing.startsWith('sdk_')) return existing;
|
|
56
56
|
const session = await authenticateViaBrowser();
|
|
57
57
|
const tok = String(session?.access_token || '').trim();
|