@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
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ const PROVIDER_CREDENTIALS = Object.freeze({
6
+ openai: Object.freeze({ env: 'OPENAI_API_KEY', files: ['openai.env'] }),
7
+ gemini: Object.freeze({ env: 'GEMINI_API_KEY', files: ['gemini.env'] }),
8
+ anthropic: Object.freeze({ env: 'ANTHROPIC_API_KEY', files: ['anthropic.env'] }),
9
+ grok: Object.freeze({ env: 'XAI_API_KEY', files: ['grok.env', 'xai.env'] }),
10
+ cloudflare: Object.freeze({ env: 'CLOUDFLARE_API_TOKEN', files: ['cloudflare.env'] }),
11
+ });
12
+
13
+ function clean(value) { return value == null ? '' : String(value).trim(); }
14
+
15
+ function homeDirectory(options = {}) {
16
+ return path.resolve(clean(options.home) || clean(options.env?.HOME) || clean(options.env?.USERPROFILE) || os.homedir());
17
+ }
18
+
19
+ function parseEnvValue(source, variable) {
20
+ for (const rawLine of String(source || '').split(/\r?\n/)) {
21
+ const line = rawLine.trim();
22
+ if (!line || line.startsWith('#')) continue;
23
+ const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
24
+ if (!match || match[1] !== variable) continue;
25
+ let value = match[2].trim();
26
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
27
+ return value;
28
+ }
29
+ return '';
30
+ }
31
+
32
+ function secureFile(filename) {
33
+ const stat = fs.statSync(filename);
34
+ if (!stat.isFile()) return { ok: false, error: 'not_a_file' };
35
+ if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) return { ok: false, error: 'permissions_too_open' };
36
+ return { ok: true, mode: stat.mode & 0o777 };
37
+ }
38
+
39
+ export function providerCredentialSpec(provider) {
40
+ const id = clean(provider).toLowerCase();
41
+ const spec = PROVIDER_CREDENTIALS[id];
42
+ return spec ? Object.freeze({ provider: id, ...spec }) : null;
43
+ }
44
+
45
+ export function resolveProviderCredential(provider, options = {}) {
46
+ const spec = providerCredentialSpec(provider);
47
+ if (!spec) return Object.freeze({ provider: clean(provider).toLowerCase(), configured: false, source: null, error: 'unsupported_provider', value: '' });
48
+ const env = options.env || process.env;
49
+ const fromEnv = clean(env?.[spec.env]);
50
+ if (fromEnv) return Object.freeze({ provider: spec.provider, configured: true, source: 'environment', env: spec.env, file: null, error: null, value: fromEnv });
51
+
52
+ const dir = path.join(homeDirectory({ ...options, env }), '.agentsam', 'env.d');
53
+ for (const basename of spec.files) {
54
+ const filename = path.join(dir, basename);
55
+ if (!fs.existsSync(filename)) continue;
56
+ try {
57
+ const safety = secureFile(filename);
58
+ if (!safety.ok) return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: safety.error, value: '' });
59
+ const value = clean(parseEnvValue(fs.readFileSync(filename, 'utf8'), spec.env));
60
+ if (value) return Object.freeze({ provider: spec.provider, configured: true, source: 'agentsam_env_file', env: spec.env, file: filename, error: null, value });
61
+ return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: 'credential_variable_missing', value: '' });
62
+ } catch (error) {
63
+ return Object.freeze({ provider: spec.provider, configured: false, source: 'agentsam_env_file', env: spec.env, file: filename, error: error?.message || String(error), value: '' });
64
+ }
65
+ }
66
+ return Object.freeze({ provider: spec.provider, configured: false, source: null, env: spec.env, file: null, error: null, value: '' });
67
+ }
68
+
69
+ export function describeProviderCredential(provider, options = {}) {
70
+ const resolved = resolveProviderCredential(provider, options);
71
+ return Object.freeze({
72
+ provider: resolved.provider,
73
+ configured: resolved.configured,
74
+ source: resolved.source,
75
+ env: resolved.env || null,
76
+ file: resolved.file || null,
77
+ error: resolved.error || null,
78
+ });
79
+ }
80
+
81
+ export function listProviderCredentialStatus(options = {}) {
82
+ return Object.freeze(Object.keys(PROVIDER_CREDENTIALS).map((provider) => describeProviderCredential(provider, options)));
83
+ }
@@ -2,31 +2,36 @@
2
2
  * Worker API template generator.
3
3
  */
4
4
 
5
- export function workerApiTemplates({ projectName, routes, cfAccountId }) {
5
+ export function workerApiTemplates({ projectName, routes, cfAccountId, dbKind = 'd1' }) {
6
6
  const files = {};
7
7
 
8
+ const pkgScripts = {
9
+ deploy: 'wrangler deploy',
10
+ dev: 'wrangler dev',
11
+ ...(dbKind === 'hyperdrive'
12
+ ? { 'db:migrate': 'psql "$DATABASE_URL" -f migrations/001_init.sql' }
13
+ : { 'db:migrate': `wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote` }),
14
+ };
15
+
8
16
  files['package.json'] = JSON.stringify({
9
17
  name: projectName,
10
18
  version: '0.1.0',
11
19
  private: true,
12
- scripts: {
13
- deploy: 'wrangler deploy',
14
- dev: 'wrangler dev',
15
- 'db:migrate': `wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote`,
16
- },
20
+ scripts: pkgScripts,
17
21
  devDependencies: { wrangler: '^3.0.0' },
22
+ ...(dbKind === 'hyperdrive' ? { dependencies: { postgres: '^3.4.0' } } : {}),
18
23
  }, null, 2);
19
24
 
25
+ const dbBindingBlock = dbKind === 'hyperdrive'
26
+ ? `[[hyperdrive]]\nbinding = "HYPERDRIVE"\nid = "REPLACE_WITH_YOUR_HYPERDRIVE_ID"\n`
27
+ : `[[d1_databases]]\nbinding = "DB"\ndatabase_name = "${projectName}"\ndatabase_id = "REPLACE_WITH_YOUR_D1_ID"\n`;
28
+
20
29
  files['wrangler.toml'] = `name = "${projectName}"
21
30
  main = "src/index.js"
22
31
  compatibility_date = "2024-01-01"
23
32
  account_id = "${cfAccountId}"
24
-
25
- [[d1_databases]]
26
- binding = "DB"
27
- database_name = "${projectName}"
28
- database_id = "REPLACE_WITH_YOUR_D1_ID"
29
- `;
33
+ ${dbKind === 'hyperdrive' ? 'compatibility_flags = ["nodejs_compat"]\n' : ''}
34
+ ${dbBindingBlock}`;
30
35
 
31
36
  // Entry
32
37
  const routeImports = routes.map(r => `import { handle${cap(r)} } from './routes/${r}.js';`).join('\n');
@@ -83,6 +88,37 @@ ${routeMatches}
83
88
  }
84
89
 
85
90
  if (routes.includes('users')) {
91
+ if (dbKind === 'hyperdrive') {
92
+ files['src/routes/users.js'] = `import postgres from 'postgres';
93
+
94
+ export async function handleUsers(request, env) {
95
+ const sql = postgres(env.HYPERDRIVE.connectionString);
96
+ const url = new URL(request.url);
97
+ const id = url.pathname.replace('/users/', '').replace('/users', '') || null;
98
+
99
+ if (request.method === 'GET' && !id) {
100
+ const rows = await sql\`SELECT * FROM users LIMIT 50\`;
101
+ return Response.json(rows);
102
+ }
103
+ if (request.method === 'GET' && id) {
104
+ const rows = await sql\`SELECT * FROM users WHERE id = \${id}\`;
105
+ if (!rows[0]) return Response.json({ error: 'Not found' }, { status: 404 });
106
+ return Response.json(rows[0]);
107
+ }
108
+ if (request.method === 'POST') {
109
+ const body = await request.json();
110
+ const newId = crypto.randomUUID();
111
+ await sql\`INSERT INTO users (id, email) VALUES (\${newId}, \${body.email})\`;
112
+ return Response.json({ id: newId }, { status: 201 });
113
+ }
114
+ if (request.method === 'DELETE' && id) {
115
+ await sql\`DELETE FROM users WHERE id = \${id}\`;
116
+ return Response.json({ ok: true });
117
+ }
118
+ return Response.json({ error: 'Method not allowed' }, { status: 405 });
119
+ }
120
+ `;
121
+ } else {
86
122
  files['src/routes/users.js'] = `export async function handleUsers(request, env) {
87
123
  const url = new URL(request.url);
88
124
  const id = url.pathname.replace('/users/', '').replace('/users', '') || null;
@@ -109,9 +145,38 @@ ${routeMatches}
109
145
  return Response.json({ error: 'Method not allowed' }, { status: 405 });
110
146
  }
111
147
  `;
148
+ }
112
149
  }
113
150
 
114
151
  if (routes.includes('content')) {
152
+ if (dbKind === 'hyperdrive') {
153
+ files['src/routes/content.js'] = `import postgres from 'postgres';
154
+
155
+ export async function handleContent(request, env) {
156
+ const sql = postgres(env.HYPERDRIVE.connectionString);
157
+ const url = new URL(request.url);
158
+ const slug = url.pathname.replace('/content/', '').replace('/content', '') || null;
159
+
160
+ if (request.method === 'GET' && !slug) {
161
+ const rows = await sql\`SELECT id, slug, title, status FROM cms_pages LIMIT 50\`;
162
+ return Response.json(rows);
163
+ }
164
+ if (request.method === 'GET' && slug) {
165
+ const rows = await sql\`SELECT * FROM cms_pages WHERE slug = \${slug}\`;
166
+ if (!rows[0]) return Response.json({ error: 'Not found' }, { status: 404 });
167
+ return Response.json(rows[0]);
168
+ }
169
+ if (request.method === 'POST') {
170
+ const body = await request.json();
171
+ const id = crypto.randomUUID();
172
+ await sql\`INSERT INTO cms_pages (id, slug, title, template, content_json)
173
+ VALUES (\${id}, \${body.slug}, \${body.title}, \${body.template ?? 'default'}, \${JSON.stringify(body.content ?? {})})\`;
174
+ return Response.json({ id }, { status: 201 });
175
+ }
176
+ return Response.json({ error: 'Method not allowed' }, { status: 405 });
177
+ }
178
+ `;
179
+ } else {
115
180
  files['src/routes/content.js'] = `export async function handleContent(request, env) {
116
181
  const url = new URL(request.url);
117
182
  const slug = url.pathname.replace('/content/', '').replace('/content', '') || null;
@@ -136,6 +201,7 @@ ${routeMatches}
136
201
  return Response.json({ error: 'Method not allowed' }, { status: 405 });
137
202
  }
138
203
  `;
204
+ }
139
205
  }
140
206
 
141
207
  if (routes.includes('webhook')) {
@@ -154,13 +220,17 @@ ${routeMatches}
154
220
  `;
155
221
  }
156
222
 
157
- // Migration
223
+ // Migration — column syntax differs: SQLite (D1) uses INTEGER/unixepoch(), Postgres uses TIMESTAMPTZ/NOW()
224
+ const createdAtCol = dbKind === 'hyperdrive'
225
+ ? 'created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()'
226
+ : 'created_at INTEGER NOT NULL DEFAULT (unixepoch())';
227
+
158
228
  let sql = `-- ${projectName} initial schema\n\n`;
159
229
  if (routes.includes('users')) {
160
230
  sql += `CREATE TABLE IF NOT EXISTS users (
161
231
  id TEXT PRIMARY KEY,
162
232
  email TEXT NOT NULL UNIQUE,
163
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
233
+ ${createdAtCol}
164
234
  );\n\n`;
165
235
  }
166
236
  if (routes.includes('content')) {
@@ -171,12 +241,23 @@ ${routeMatches}
171
241
  template TEXT NOT NULL DEFAULT 'default',
172
242
  content_json TEXT,
173
243
  status TEXT NOT NULL DEFAULT 'draft',
174
- created_at INTEGER NOT NULL DEFAULT (unixepoch())
244
+ ${createdAtCol}
175
245
  );\n\n`;
176
246
  }
177
247
 
178
248
  files['migrations/001_init.sql'] = sql;
179
249
 
250
+ const deploySteps = dbKind === 'hyperdrive'
251
+ ? `npm install
252
+ npx wrangler hyperdrive create ${projectName} --connection-string="postgres://user:pass@host:5432/db"
253
+ # paste the returned Hyperdrive id into wrangler.toml as HYPERDRIVE binding
254
+ npx wrangler deploy`
255
+ : `npm install
256
+ npx wrangler d1 create ${projectName}
257
+ # paste database_id into wrangler.toml
258
+ npx wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote
259
+ npx wrangler deploy`;
260
+
180
261
  files['README.md'] = `# ${projectName}
181
262
 
182
263
  Scaffolded by [@inneranimalmedia/agentsam-sdk](https://github.com/SamPrimeaux/agentsam-sdk).
@@ -185,14 +266,14 @@ Scaffolded by [@inneranimalmedia/agentsam-sdk](https://github.com/SamPrimeaux/ag
185
266
 
186
267
  ${routes.map(r => `- \`/${r}\``).join('\n')}
187
268
 
269
+ ## Database
270
+
271
+ ${dbKind === 'hyperdrive' ? 'Your own Postgres, via Cloudflare Hyperdrive.' : 'Cloudflare D1 (SQLite).'}
272
+
188
273
  ## Deploy
189
274
 
190
275
  \`\`\`bash
191
- npm install
192
- npx wrangler d1 create ${projectName}
193
- # paste database_id into wrangler.toml
194
- npx wrangler d1 execute ${projectName} --file=migrations/001_init.sql --remote
195
- npx wrangler deploy
276
+ ${deploySteps}
196
277
  \`\`\`
197
278
  `;
198
279
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  import {
7
7
  text,
8
+ select,
8
9
  multiselect,
9
10
  confirm,
10
11
  spinner,
@@ -42,6 +43,15 @@ export async function runWorkerApiWizard() {
42
43
  });
43
44
  if (isCancel(routes)) { cancel('Cancelled.'); process.exit(0); }
44
45
 
46
+ const dbKind = await select({
47
+ message: 'Database?',
48
+ options: [
49
+ { value: 'd1', label: 'Cloudflare D1', hint: 'Native, free tier, SQLite' },
50
+ { value: 'hyperdrive', label: 'Your own Postgres (Hyperdrive)', hint: 'Supabase, Neon, RDS, etc.' },
51
+ ],
52
+ });
53
+ if (isCancel(dbKind)) { cancel('Cancelled.'); process.exit(0); }
54
+
45
55
  const cfAccountId = await text({
46
56
  message: 'Cloudflare account ID?',
47
57
  placeholder: 'abc123...',
@@ -57,20 +67,26 @@ export async function runWorkerApiWizard() {
57
67
  const s = spinner();
58
68
  s.start('Generating files...');
59
69
 
60
- const config = { projectName: projectName.trim(), routes, cfAccountId: cfAccountId.trim() };
70
+ const config = { projectName: projectName.trim(), routes, cfAccountId: cfAccountId.trim(), dbKind };
61
71
  const fileTree = workerApiTemplates(config);
62
72
  await writeFileTree(`./${config.projectName}`, fileTree);
63
73
 
64
74
  s.stop(pc.green(`Files written to ./${config.projectName}/`));
65
75
 
66
- note(
67
- [
68
- `cd ${config.projectName}`,
69
- `npm install`,
70
- `npx wrangler d1 create ${config.projectName}`,
71
- `npx wrangler d1 execute ${config.projectName} --file=migrations/001_init.sql --remote`,
72
- `npx wrangler deploy`,
73
- ].join('\n'),
74
- 'Next steps'
75
- );
76
+ const nextSteps = dbKind === 'hyperdrive'
77
+ ? [
78
+ `cd ${config.projectName}`,
79
+ `npm install`,
80
+ `npx wrangler hyperdrive create ${config.projectName} --connection-string="postgres://user:pass@host:5432/db"`,
81
+ `# paste the returned Hyperdrive id into wrangler.toml`,
82
+ `npx wrangler deploy`,
83
+ ]
84
+ : [
85
+ `cd ${config.projectName}`,
86
+ `npm install`,
87
+ `npx wrangler d1 create ${config.projectName}`,
88
+ `npx wrangler d1 execute ${config.projectName} --file=migrations/001_init.sql --remote`,
89
+ `npx wrangler deploy`,
90
+ ];
91
+ note(nextSteps.join('\n'), 'Next steps');
76
92
  }
@@ -1,35 +1,41 @@
1
- /**
2
- * Canonical slash-command surface for Agent Sam SDK CLI / shell UX.
3
- * Consumed by the interactive `agentsam shell` REPL and presentation layers.
4
- */
1
+ /** Canonical, implemented slash-command surface for Agent Sam SDK CLI. */
5
2
 
6
3
  export const SHELL_THEMES = ['NIGHT', 'DAY', 'LAVA', 'VOID'];
7
4
 
8
- /** @type {Array<{ cmd: string, description: string, lane?: string }>} */
9
5
  export const SLASH_COMMANDS = [
10
- { cmd: '/help', description: 'Show Agent Sam commands' },
6
+ { cmd: '/model', description: 'Choose exact model, reasoning level, and processing tier', lane: 'model' },
7
+ { cmd: '/reasoning', description: 'Set reasoning effort for the selected model', lane: 'model' },
8
+ { cmd: '/fast', description: 'Use provider Fast processing when the selected model supports it', lane: 'model' },
9
+ { cmd: '/flex', description: 'Use provider Flex processing when the selected model supports it', lane: 'model' },
10
+ { cmd: '/standard', description: 'Return to Standard provider processing', lane: 'model' },
11
+ { cmd: '/context', description: 'Show model context economics; add repo for Git bridge context', lane: 'context' },
11
12
  { cmd: '/status', description: 'Local project, DB, Git, and PTY health', lane: 'local' },
12
- { cmd: '/context', description: 'Current Git repository and revision', lane: 'git' },
13
+ { cmd: '/models', description: 'Probe providers and provider-verified known models', lane: 'model' },
14
+ { cmd: '/login', description: 'Authenticate IAM and save the machine-local Agent Sam session', lane: 'identity' },
15
+ { cmd: '/logout', description: 'Remove the local IAM session without deleting provider keys', lane: 'identity' },
16
+ { cmd: '/whoami', description: 'Show authenticated IAM identity and safe credential status', lane: 'identity' },
17
+ { cmd: '/session', description: 'Show current session usage, cost, and resume receipt', lane: 'observability' },
18
+ { cmd: '/cf', description: 'Cloudflare native reads, Wrangler status, and CPU profile analysis', lane: 'cloudflare' },
19
+ { cmd: '/settings', description: 'Choose project, runtime, terminal, and model policy' },
13
20
  { cmd: '/pwd', description: 'Print working directory', lane: 'terminal' },
14
21
  { cmd: '/cd', description: 'Change working directory', lane: 'terminal' },
15
- { cmd: '/git', description: 'Git status, diff, branch, commit, and remote', lane: 'git' },
22
+ { cmd: '/git', description: 'Run an explicit Git subcommand', lane: 'git' },
23
+ { cmd: '/diff', description: 'Show the current Git diff', lane: 'git' },
16
24
  { cmd: '/db', description: 'Local SQLite status and query helpers', lane: 'data' },
17
- { cmd: '/agent', description: 'Send a goal to the configured Agent Sam', lane: 'agent' },
18
- { cmd: '/models', description: 'Show available model providers and local models' },
19
- { cmd: '/settings', description: 'Choose project, runtime, terminal, and model preference' },
25
+ { cmd: '/agent', description: 'Send a goal to the configured local Agent Sam runtime', lane: 'agent' },
20
26
  { cmd: '/logs', description: 'Show local Agent Sam execution events', lane: 'observability' },
21
27
  { cmd: '/deploy', description: 'Add a cloud adapter and deploy intentionally', lane: 'deploy' },
28
+ { cmd: '/clear', description: 'Clear the terminal display', lane: 'terminal' },
29
+ { cmd: '/help', description: 'Show the factual implemented command catalog' },
22
30
  { cmd: '/exit', description: 'Exit Agent Sam shell and return to the host terminal' },
23
31
  ];
24
32
 
25
- /** Shell UX rollout phases (gorilla-shell → SDK default CLI experience). */
26
33
  export const SHELL_PHASES = [
27
34
  { id: '0-prototype', label: 'Visual prototype + demo scenarios', status: 'complete' },
28
- { id: 'pty-connection', label: 'Local PTY via agentsam start-local', status: 'current' },
29
- { id: 'hud-layer', label: 'Quest log, tool gate, XP HUD', status: 'planned' },
30
- { id: 'buddy-system', label: 'In-shell Agent Sam via MCP', status: 'planned' },
35
+ { id: 'pty-connection', label: 'Local PTY via agentsam start-local', status: 'complete' },
36
+ { id: 'model-context-controls', label: 'Model, reasoning, processing, and context economics', status: 'complete' },
37
+ { id: 'run-telemetry', label: 'Provider usage, cost, resumable sessions, and permission receipts', status: 'current' },
31
38
  { id: 'dashboard-embed', label: 'Embeddable shell for IAM dashboard', status: 'planned' },
32
- { id: 'standalone-pwa', label: 'Installable PWA / SDK default shell', status: 'planned' },
33
39
  ];
34
40
 
35
41
  export function listSlashCommands(opts = {}) {
@@ -0,0 +1,135 @@
1
+ export const MODEL_CATALOG_SCHEMA = 'agentsam-model-catalog-v1';
2
+
3
+ const ASTRA_SOURCE = 'https://developers.openai.com/api/docs/models/gpt-6-astra';
4
+
5
+ const GPT_6_ASTRA = Object.freeze({
6
+ schema_version: 1,
7
+ model_key: 'openai:gpt-6-astra',
8
+ provider: 'openai',
9
+ provider_model_id: 'gpt-6-astra',
10
+ label: 'GPT-6 Astra',
11
+ context_window: 1_050_000,
12
+ max_output_tokens: 128_000,
13
+ reasoning_efforts: Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']),
14
+ service_tiers: Object.freeze(['default', 'fast', 'flex']),
15
+ capabilities: Object.freeze({
16
+ responses: true,
17
+ streaming: true,
18
+ function_calling: true,
19
+ structured_outputs: true,
20
+ prompt_caching: true,
21
+ compaction: true,
22
+ tool_search: true,
23
+ async_tool_calls: true,
24
+ mid_turn_steering: true,
25
+ configuration_update: true,
26
+ batch: true,
27
+ fast: true,
28
+ flex: true,
29
+ }),
30
+ pricing: Object.freeze({
31
+ currency: 'USD',
32
+ unit: 'per_million_tokens',
33
+ input: 10,
34
+ cached_input: 1,
35
+ cache_write: 12.5,
36
+ output: 50,
37
+ thresholds: Object.freeze([
38
+ Object.freeze({
39
+ input_tokens_gt: 272_000,
40
+ applies_to_full_request: true,
41
+ multipliers: Object.freeze({ input: 2, cached_input: 2, cache_write: 2, output: 1.5 }),
42
+ }),
43
+ ]),
44
+ service_tier_multipliers: Object.freeze({ default: 1, fast: 2, flex: 0.5, batch: 0.5 }),
45
+ source: ASTRA_SOURCE,
46
+ as_of: '2026-09-12',
47
+ }),
48
+ context_policy: Object.freeze({
49
+ target_input_tokens: 120_000,
50
+ compact_at_tokens: 180_000,
51
+ intervene_at_tokens: 220_000,
52
+ max_normal_input_tokens: 250_000,
53
+ pricing_threshold_tokens: 272_000,
54
+ safety_margin_tokens: 22_000,
55
+ }),
56
+ batch: Object.freeze({
57
+ supported: true,
58
+ completion_window: '24h',
59
+ relative_price: 0.5,
60
+ interactive: false,
61
+ }),
62
+ source: Object.freeze({ url: ASTRA_SOURCE, as_of: '2026-09-12' }),
63
+ });
64
+
65
+ export const MODEL_CATALOG = Object.freeze([GPT_6_ASTRA]);
66
+
67
+ function clean(value) {
68
+ return value == null ? '' : String(value).trim();
69
+ }
70
+
71
+ function nonNegativeInteger(value, label) {
72
+ const number = Number(value ?? 0);
73
+ if (!Number.isFinite(number) || number < 0) throw new RangeError(`${label} must be a non-negative number`);
74
+ return Math.floor(number);
75
+ }
76
+
77
+ export function listModelCatalog(options = {}) {
78
+ const provider = clean(options.provider).toLowerCase();
79
+ return MODEL_CATALOG.filter((row) => !provider || row.provider === provider);
80
+ }
81
+
82
+ export function getModelRecord(value) {
83
+ const key = clean(value);
84
+ if (!key) return null;
85
+ return MODEL_CATALOG.find((row) => row.model_key === key || row.provider_model_id === key) || null;
86
+ }
87
+
88
+ export function calculateModelCost(model, usage = {}, options = {}) {
89
+ const record = typeof model === 'string' ? getModelRecord(model) : model;
90
+ if (!record?.pricing) throw new TypeError('model pricing record is required');
91
+
92
+ const totalInput = nonNegativeInteger(usage.input_tokens ?? usage.inputTokens, 'input_tokens');
93
+ const cachedInput = Math.min(totalInput, nonNegativeInteger(usage.cached_input_tokens ?? usage.cachedInputTokens, 'cached_input_tokens'));
94
+ const cacheWrite = Math.min(totalInput - cachedInput, nonNegativeInteger(usage.cache_write_tokens ?? usage.cacheWriteTokens, 'cache_write_tokens'));
95
+ const uncachedInput = Math.max(0, totalInput - cachedInput - cacheWrite);
96
+ const output = nonNegativeInteger(usage.output_tokens ?? usage.outputTokens, 'output_tokens');
97
+ const serviceTier = clean(options.serviceTier || usage.service_tier || usage.serviceTier || 'default') || 'default';
98
+ const tierMultiplier = record.pricing.service_tier_multipliers?.[serviceTier];
99
+ if (!Number.isFinite(tierMultiplier)) throw new RangeError(`unsupported service tier for ${record.provider_model_id}: ${serviceTier}`);
100
+
101
+ const threshold = (record.pricing.thresholds || []).find((row) => totalInput > row.input_tokens_gt) || null;
102
+ const thresholdMultipliers = threshold?.multipliers || {};
103
+ const rates = {
104
+ input: record.pricing.input * (thresholdMultipliers.input || 1) * tierMultiplier,
105
+ cached_input: record.pricing.cached_input * (thresholdMultipliers.cached_input || 1) * tierMultiplier,
106
+ cache_write: record.pricing.cache_write * (thresholdMultipliers.cache_write || 1) * tierMultiplier,
107
+ output: record.pricing.output * (thresholdMultipliers.output || 1) * tierMultiplier,
108
+ };
109
+ const components = {
110
+ input: (uncachedInput * rates.input) / 1_000_000,
111
+ cached_input: (cachedInput * rates.cached_input) / 1_000_000,
112
+ cache_write: (cacheWrite * rates.cache_write) / 1_000_000,
113
+ output: (output * rates.output) / 1_000_000,
114
+ };
115
+ const total = Object.values(components).reduce((sum, value) => sum + value, 0);
116
+
117
+ return Object.freeze({
118
+ model_key: record.model_key,
119
+ service_tier: serviceTier,
120
+ usage: Object.freeze({
121
+ input_tokens: totalInput,
122
+ uncached_input_tokens: uncachedInput,
123
+ cached_input_tokens: cachedInput,
124
+ cache_write_tokens: cacheWrite,
125
+ output_tokens: output,
126
+ }),
127
+ threshold_applied: threshold ? Object.freeze({ ...threshold }) : null,
128
+ rates_per_million: Object.freeze(rates),
129
+ components_usd: Object.freeze(components),
130
+ total_usd: total,
131
+ estimate_kind: usage.estimate_kind === 'provider' ? 'provider' : 'local',
132
+ pricing_source: record.pricing.source,
133
+ pricing_as_of: record.pricing.as_of,
134
+ });
135
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ MODEL_CATALOG_SCHEMA,
3
+ MODEL_CATALOG,
4
+ listModelCatalog,
5
+ getModelRecord,
6
+ calculateModelCost,
7
+ } from './catalog.js';
@@ -0,0 +1,5 @@
1
+ export {
2
+ createOpenAIResponsesAdapter,
3
+ extractOpenAIOutputText,
4
+ extractOpenAIFunctionCalls,
5
+ } from './openai-responses.js';