@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.
Files changed (112) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +12 -8
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/CLI_SHELL.md +163 -53
  6. package/docs/RELEASES.md +16 -7
  7. package/package.json +20 -8
  8. package/packages/connectors/cloudflare/package.json +10 -0
  9. package/packages/connectors/cloudflare/src/index.js +127 -0
  10. package/packages/connectors/cloudflare/src/owner.js +76 -0
  11. package/packages/connectors/cloudflare/src/routes.js +223 -0
  12. package/packages/connectors/cloudflare/src/vault.js +80 -0
  13. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  14. package/packages/identity/package.json +2 -2
  15. package/packages/identity/src/contracts/auth-config.js +18 -7
  16. package/packages/identity/tests/auth-config.test.mjs +9 -5
  17. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  18. package/protocol/README.md +1 -0
  19. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  20. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  21. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  22. package/protocol/capabilities/manifest.json +47 -0
  23. package/protocol/context/context-budget.schema.json +10 -15
  24. package/protocol/context/context-item.schema.json +4 -5
  25. package/protocol/context/resolved-context-pack.schema.json +19 -14
  26. package/protocol/models/README.md +373 -0
  27. package/protocol/models/model-inventory-v2.schema.json +212 -0
  28. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  29. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  30. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  31. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  32. package/skills/catalog.json +18 -0
  33. package/src/agent/capability-adapter.js +25 -13
  34. package/src/agent/index.js +1 -0
  35. package/src/agent/responses-runner.js +325 -0
  36. package/src/cli.js +98 -28
  37. package/src/cloudflare/cpu-profile.js +115 -0
  38. package/src/cloudflare/index.js +14 -0
  39. package/src/cloudflare/wrangler.js +132 -0
  40. package/src/commands/account-auth.js +47 -0
  41. package/src/commands/cloudflare.js +58 -0
  42. package/src/commands/connections.js +93 -0
  43. package/src/commands/context-economics.js +114 -0
  44. package/src/commands/deploy.js +39 -3
  45. package/src/commands/eval.js +63 -0
  46. package/src/commands/interactive.js +2 -5
  47. package/src/commands/models.js +85 -40
  48. package/src/commands/preferences.js +101 -59
  49. package/src/commands/resume.js +67 -0
  50. package/src/commands/security.js +5 -3
  51. package/src/commands/shell.js +370 -109
  52. package/src/commands/tunnel.js +2 -2
  53. package/src/commands/whoami.js +86 -0
  54. package/src/context/budget.js +68 -6
  55. package/src/context/index.js +3 -1
  56. package/src/context/rehydrate.js +35 -0
  57. package/src/context/resolve.js +44 -12
  58. package/src/errors/diagnostic.js +160 -0
  59. package/src/errors/index.js +9 -0
  60. package/src/eval/context.js +191 -0
  61. package/src/eval/index.js +1 -0
  62. package/src/index.js +55 -1
  63. package/src/lib/account-session.js +98 -0
  64. package/src/lib/agent-instructions.js +73 -0
  65. package/src/lib/auth.js +4 -0
  66. package/src/lib/cli-preferences.js +28 -24
  67. package/src/lib/deploy/git-guard.js +69 -0
  68. package/src/lib/deploy/health.js +57 -0
  69. package/src/lib/deploy/local-studio.js +283 -0
  70. package/src/lib/deploy/secret-scan.js +65 -0
  71. package/src/lib/detect-context.js +2 -2
  72. package/src/lib/execution-approvals.js +59 -0
  73. package/src/lib/local-sessions.js +127 -0
  74. package/src/lib/provider-credentials.js +83 -0
  75. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  76. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  77. package/src/lib/slash-commands.js +22 -16
  78. package/src/models/catalog.js +135 -0
  79. package/src/models/index.js +7 -0
  80. package/src/providers/index.js +5 -0
  81. package/src/providers/openai-responses.js +275 -0
  82. package/src/security/process.js +35 -9
  83. package/src/telemetry/contracts.js +203 -0
  84. package/src/telemetry/events.js +48 -0
  85. package/src/telemetry/index.js +8 -0
  86. package/src/tools/hydrate.js +35 -0
  87. package/src/tools/index.js +1 -0
  88. package/src/ui/boot.js +15 -17
  89. package/test/account-session.test.mjs +36 -0
  90. package/test/cli-preferences.test.mjs +26 -5
  91. package/test/cloudflare-connector.test.mjs +96 -0
  92. package/test/cloudflare-runtime.test.mjs +75 -0
  93. package/test/context.test.mjs +61 -12
  94. package/test/deploy-health-scan.test.mjs +67 -0
  95. package/test/error-diagnostics.test.mjs +59 -0
  96. package/test/eval-context.test.mjs +37 -0
  97. package/test/execution-approvals.test.mjs +27 -0
  98. package/test/local-sessions.test.mjs +42 -0
  99. package/test/local-studio-deploy.test.mjs +83 -0
  100. package/test/model-catalog.test.mjs +43 -0
  101. package/test/models.test.mjs +30 -16
  102. package/test/npm10-lock.test.mjs +29 -0
  103. package/test/openai-responses.test.mjs +95 -0
  104. package/test/provider-credentials.test.mjs +52 -0
  105. package/test/rehydrate.test.mjs +25 -0
  106. package/test/release-hygiene.test.mjs +4 -4
  107. package/test/responses-runner.test.mjs +148 -0
  108. package/test/shell.test.mjs +47 -20
  109. package/test/smoke.mjs +4 -1
  110. package/test/telemetry.test.mjs +79 -0
  111. package/test/tools-search.test.mjs +14 -1
  112. package/test/whoami-resume.test.mjs +56 -0
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import test from 'node:test';
7
7
  import { dispatchShellLine, renderShellCatalog, renderShellPrompt, tokenizeShellLine } from '../src/commands/shell.js';
8
+ import { readCliPreferences, writeCliPreferences } from '../src/lib/cli-preferences.js';
8
9
 
9
10
  const repoRoot = path.resolve(new URL('..', import.meta.url).pathname);
10
11
 
@@ -21,47 +22,73 @@ test('interactive prompt derives username and cwd instead of hardcoding Agent Sa
21
22
  assert.equal(renderShellPrompt('/tmp/demo', env), 'alice /tmp/demo > ');
22
23
  });
23
24
 
24
- test('shell catalog advertises commands that the REPL owns', () => {
25
+ test('shell catalog only advertises implemented core controls', () => {
25
26
  const catalog = renderShellCatalog();
26
- assert.match(catalog, /\/help\s+Show Agent Sam commands/);
27
- assert.match(catalog, /\/status\s+Local project/);
28
- assert.match(catalog, /\/exit\s+Exit Agent Sam shell/);
27
+ for (const command of ['/model', '/reasoning', '/fast', '/flex', '/standard', '/context', '/cf', '/diff', '/clear', '/exit']) {
28
+ assert.match(catalog, new RegExp(command.replace('/', '\\/')));
29
+ }
30
+ assert.match(catalog, /scrollable command picker/);
29
31
  });
30
32
 
31
- test('dispatch handles help, pwd, cd, and exit without falling through to host shell', async () => {
33
+ test('dispatch handles help, menu fallback, pwd, cd, and exit without falling through to host shell', async () => {
32
34
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-shell-'));
33
35
  const child = path.join(root, 'child folder');
34
36
  fs.mkdirSync(child);
35
37
  let output = '';
36
- const state = { cwd: root, write: (text) => { output += text; } };
37
-
38
+ const state = { cwd: root, write: (text) => { output += text; }, interactive: false };
38
39
  let result = await dispatchShellLine('/help', state);
39
40
  assert.equal(result.handled, true);
40
- assert.equal(result.exit, false);
41
41
  assert.match(output, /Slash commands/);
42
-
43
42
  output = '';
44
- result = await dispatchShellLine('/pwd', state);
45
- assert.equal(result.handled, true);
43
+ await dispatchShellLine('/', state);
44
+ assert.match(output, /Agent Sam Terminal/);
45
+ output = '';
46
+ await dispatchShellLine('/pwd', state);
46
47
  assert.equal(output.trim(), root);
47
-
48
48
  output = '';
49
- result = await dispatchShellLine('/cd "child folder"', state);
50
- assert.equal(result.handled, true);
49
+ await dispatchShellLine('/cd "child folder"', state);
51
50
  assert.equal(state.cwd, child);
52
51
  assert.equal(output.trim(), child);
53
-
54
52
  result = await dispatchShellLine('/exit', state);
55
53
  assert.equal(result.exit, true);
56
54
  });
57
55
 
56
+ test('reasoning and service-tier commands persist only supported controls for an exact model', async () => {
57
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-shell-model-'));
58
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'model-demo' }));
59
+ writeCliPreferences(root, { trustedDirectory: true, modelPreference: 'openai:gpt-6-astra', reasoningEffort: 'low', serviceTier: 'default' });
60
+ let output = '';
61
+ const state = { cwd: root, write: (text) => { output += text; }, interactive: false };
62
+ await dispatchShellLine('/reasoning high', state);
63
+ assert.equal(readCliPreferences(root).reasoningEffort, 'high');
64
+ await dispatchShellLine('/fast', state);
65
+ assert.equal(readCliPreferences(root).serviceTier, 'fast');
66
+ await dispatchShellLine('/flex', state);
67
+ assert.equal(readCliPreferences(root).serviceTier, 'flex');
68
+ await dispatchShellLine('/standard', state);
69
+ assert.equal(readCliPreferences(root).serviceTier, 'default');
70
+ assert.match(output, /reasoning → high/);
71
+ assert.match(output, /Fast is a paid latency choice/);
72
+ assert.match(output, /Flex trades latency/);
73
+ });
74
+
75
+ test('bare /context shows truthful economics without inventing active token usage', async () => {
76
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-shell-context-'));
77
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'context-demo' }));
78
+ writeCliPreferences(root, { trustedDirectory: true, modelPreference: 'openai:gpt-6-astra', reasoningEffort: 'medium', serviceTier: 'default' });
79
+ let output = '';
80
+ await dispatchShellLine('/context', { cwd: root, write: (text) => { output += text; }, interactive: false });
81
+ assert.match(output, /1,050,000/);
82
+ assert.match(output, /272,000/);
83
+ assert.match(output, /180,000/);
84
+ assert.match(output, /unavailable · no provider\/local usage snapshot yet/);
85
+ assert.match(output, /Batch is a separate asynchronous execution lane/);
86
+ });
87
+
58
88
  test('CLI supports a deterministic one-shot slash command for regression tests', () => {
59
- const result = spawnSync(process.execPath, ['src/cli.js', 'shell', '--command', '/help'], {
60
- cwd: repoRoot,
61
- encoding: 'utf8',
62
- });
89
+ const result = spawnSync(process.execPath, ['src/cli.js', 'shell', '--command', '/help'], { cwd: repoRoot, encoding: 'utf8' });
63
90
  assert.equal(result.status, 0, result.stderr);
64
91
  assert.match(result.stdout, /Agent Sam Terminal/);
65
- assert.match(result.stdout, /\/help/);
92
+ assert.match(result.stdout, /\/model/);
66
93
  assert.match(result.stdout, /\/exit/);
67
94
  });
package/test/smoke.mjs CHANGED
@@ -55,9 +55,12 @@ await assert.rejects(() => finalizeInboundOAuth({}, new Request('https://x'), {}
55
55
  assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/deploy'));
56
56
  assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/db'));
57
57
  assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/agent'));
58
+ assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/model'));
59
+ assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/reasoning'));
60
+ assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/fast'));
58
61
  assert.deepEqual(
59
62
  listSlashCommands({ lane: 'deploy' }).map(({ cmd }) => cmd),
60
- ['/help', '/models', '/settings', '/deploy', '/exit'],
63
+ ['/settings', '/deploy', '/help', '/exit'],
61
64
  'deploy lane keeps global shell controls and excludes commands from other lanes',
62
65
  );
63
66
 
@@ -0,0 +1,79 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createAgentEvent, createUsageSnapshot } from '../src/telemetry/index.js';
4
+
5
+ test('AgentEvent is compact serializable provider-neutral telemetry', () => {
6
+ const event = createAgentEvent('context.compaction.completed', { before: 181_000, after: 72_000 }, {
7
+ runId: 'run_test', sequence: 4, timestamp: '2026-09-12T00:00:00.000Z',
8
+ });
9
+ assert.equal(event.type, 'context.compaction.completed');
10
+ assert.equal(event.run_id, 'run_test');
11
+ assert.doesNotThrow(() => JSON.stringify(event));
12
+ });
13
+
14
+ test('current active context and cumulative run usage are different metrics', () => {
15
+ const usage = createUsageSnapshot({
16
+ current_context: { input_tokens: 48_000, window_tokens: 1_050_000 },
17
+ cumulative: { input_tokens: 310_000, output_tokens: 22_000, cached_input_tokens: 120_000, cache_write_tokens: 12_000, reasoning_tokens: 8_000 },
18
+ estimate_kind: 'provider',
19
+ });
20
+ assert.equal(usage.current_context.input_tokens, 48_000);
21
+ assert.equal(usage.cumulative.input_tokens, 310_000);
22
+ assert.equal(usage.provider_authoritative, true);
23
+ });
24
+
25
+ import {
26
+ createRunReceipt,
27
+ createUsageReceipt,
28
+ createApprovalReceipt,
29
+ createTerminalJobReceipt,
30
+ } from '../src/telemetry/index.js';
31
+
32
+ test('runtime receipts are account-owned and emit no tenant/workspace/user ownership aliases', () => {
33
+ const run = createRunReceipt({
34
+ id: 'arun_1', account_id: 'au_1', mode: 'agent', status: 'running',
35
+ source_client: 'cli', surface: 'cli', reasoning_effort: 'high', requested_service_tier: 'fast',
36
+ });
37
+ assert.equal(run.account_id, 'au_1');
38
+ assert.equal(run.source_client, 'cli');
39
+ assert.equal(run.requested_service_tier, 'fast');
40
+ assert.equal('user_id' in run, false);
41
+ assert.equal('tenant_id' in run, false);
42
+ assert.equal('workspace_id' in run, false);
43
+ });
44
+
45
+ test('usage receipt models exactly one provider call and keeps cache/reasoning economics', () => {
46
+ const usage = createUsageReceipt({
47
+ id: 'ue_1', account_id: 'au_1', agent_run_id: 'arun_1', provider: 'openai', model_key: 'gpt-6-astra',
48
+ model_call_index: 2, provider_request_id: 'resp_1', input_tokens: 1000, cached_input_tokens: 600,
49
+ cache_write_tokens: 20, output_tokens: 100, reasoning_tokens: 40, cost_usd: 0.12, cost_basis: 'provider',
50
+ });
51
+ assert.equal(usage.total_tokens, 1100);
52
+ assert.equal(usage.cached_input_tokens, 600);
53
+ assert.equal(usage.reasoning_tokens, 40);
54
+ assert.equal(usage.model_call_index, 2);
55
+ });
56
+
57
+ test('approval and terminal receipts preserve lineage without turning D1 into transcript storage', () => {
58
+ const approval = createApprovalReceipt({
59
+ id: 'appr_1', account_id: 'au_1', agent_run_id: 'arun_1', tool_call_id: 'call_1', terminal_job_id: 'tjob_1',
60
+ action_summary: 'Deploy worker', sanitizedInput: { worker: 'api' }, risk_level: 'high',
61
+ });
62
+ assert.equal(approval.terminal_job_id, 'tjob_1');
63
+ assert.match(approval.sanitized_input_json, /worker/);
64
+
65
+ const job = createTerminalJobReceipt({
66
+ id: 'tjob_1', account_id: 'au_1', instance_id: 'tinst_1', connection_id: 'conn_1',
67
+ execos_run_id: 'xrun_1', status: 'running', idempotency_key: 'idem_1', max_attempts: 1,
68
+ });
69
+ assert.equal(job.execos_run_id, 'xrun_1');
70
+ assert.equal(job.max_attempts, 1);
71
+ assert.equal('stdout' in job, false);
72
+ assert.equal('stderr' in job, false);
73
+ assert.equal('command' in job, false);
74
+ });
75
+
76
+ test('runtime receipts refuse model-invented account identity', () => {
77
+ assert.throws(() => createRunReceipt({ id: 'arun_1' }), /account_id is required/);
78
+ assert.throws(() => createUsageReceipt({ id: 'ue_1', provider: 'openai', model_key: 'gpt-6-astra' }), /account_id is required/);
79
+ });
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
  import { getToolCatalog } from '../src/lib/tools.js';
4
- import { searchToolCards } from '../src/tools/index.js';
4
+ import { hydrateToolSchemas, searchToolCards } from '../src/tools/index.js';
5
5
  import { DEFAULT_RESULT_POLICY } from '../src/context/index.js';
6
6
 
7
7
  test('every built-in SDK tool inherits a bounded result policy', () => {
@@ -25,3 +25,16 @@ test('tools.search returns compact cards rather than full schemas', () => {
25
25
  assert.equal('input_schema' in result.cards[0], false);
26
26
  assert.ok(result.receipt.chars < 24_000);
27
27
  });
28
+
29
+ test('selected schemas hydrate without loading the full tool catalog', () => {
30
+ const catalog = Array.from({ length: 50 }, (_, index) => ({
31
+ name: `tool.${index}`,
32
+ description: `Capability ${index}`,
33
+ input_schema: { type: 'object', properties: { value: { type: 'string' }, padding: { description: 'x'.repeat(300) } } },
34
+ }));
35
+ const result = hydrateToolSchemas(catalog, ['tool.4', 'tool.22'], { maxTools: 4, maxChars: 5_000 });
36
+ assert.deepEqual(result.tools.map((tool) => tool.name), ['tool.4', 'tool.22']);
37
+ assert.equal(result.receipt.catalog_items, 50);
38
+ assert.equal(result.receipt.hydrated_tools, 2);
39
+ assert.ok(result.receipt.schema_chars < 5_000);
40
+ });
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+ import { saveAccountSession } from '../src/lib/account-session.js';
7
+ import { createLocalSession } from '../src/lib/local-sessions.js';
8
+ import { collectWhoami } from '../src/commands/whoami.js';
9
+ import { runResume } from '../src/commands/resume.js';
10
+
11
+ function tempHome(t) {
12
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-ux-home-'));
13
+ t.after(() => fs.rmSync(home, { recursive: true, force: true }));
14
+ return home;
15
+ }
16
+
17
+ test('whoami validates persisted IAM identity while never returning the SDK or provider secret', async t => {
18
+ const home = tempHome(t);
19
+ saveAccountSession({ access_token: 'sdk_do_not_print', user_id: 'au_local' }, { home });
20
+ const envDir = path.join(home, '.agentsam', 'env.d');
21
+ fs.mkdirSync(envDir, { recursive: true });
22
+ const openaiFile = path.join(envDir, 'openai.env');
23
+ fs.writeFileSync(openaiFile, 'OPENAI_API_KEY=sk-never-print-this\n', { mode: 0o600 });
24
+ if (process.platform !== 'win32') fs.chmodSync(openaiFile, 0o600);
25
+
26
+ const status = await collectWhoami({
27
+ env: {}, home,
28
+ contextLoader: async token => {
29
+ assert.equal(token, 'sdk_do_not_print');
30
+ return { user_id: 'au_server', account_id: 'acct_server', email: 'dev@example.test', cloudflare: { ok: true }, byok: { openai: { configured: true, masked: 'secret' } } };
31
+ },
32
+ });
33
+ assert.equal(status.authenticated, true);
34
+ assert.equal(status.identity.account_id, 'acct_server');
35
+ assert.equal(status.sdk_credential.source, 'agentsam_account_session');
36
+ assert.equal(status.provider_credentials.find(row => row.provider === 'openai').configured, true);
37
+ const serialized = JSON.stringify(status);
38
+ assert.doesNotMatch(serialized, /sdk_do_not_print|sk-never-print-this|masked/);
39
+ });
40
+
41
+ test('resume restores saved cwd and session through the canonical shell runtime', async t => {
42
+ const home = tempHome(t);
43
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-resume-project-'));
44
+ t.after(() => fs.rmSync(cwd, { recursive: true, force: true }));
45
+ const session = createLocalSession({ cwd, title: 'Run wrangler whoami', model_key: 'openai:gpt-6-astra' }, { home });
46
+ let called = null;
47
+ const restored = await runResume([session.id], {
48
+ home,
49
+ write() {},
50
+ runShellImpl: async (argv, options) => { called = { argv, options }; },
51
+ });
52
+ assert.equal(restored.id, session.id);
53
+ assert.deepEqual(called.argv, []);
54
+ assert.equal(called.options.cwd, cwd);
55
+ assert.equal(called.options.session.id, session.id);
56
+ });