@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,223 @@
1
+ import {
2
+ CLOUDFLARE_CALLBACK_PATH,
3
+ CLOUDFLARE_OAUTH_TOKEN_URL,
4
+ CLOUDFLARE_OAUTH_REVOKE_URL,
5
+ buildAuthorizeUrl,
6
+ cloudflareConnectionSafeStatus,
7
+ resolveCloudflareOAuthClient,
8
+ requestedCloudflareScopes,
9
+ assertConnectionOwner,
10
+ } from './index.js';
11
+ import { resolveAuthenticatedOwner } from './owner.js';
12
+ import { sealToken, vaultConfigured } from './vault.js';
13
+
14
+ function json(data, status = 200) {
15
+ return new Response(JSON.stringify(data), {
16
+ status,
17
+ headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },
18
+ });
19
+ }
20
+
21
+ function redirect(location) {
22
+ return new Response(null, { status: 302, headers: { location } });
23
+ }
24
+
25
+ export function isCloudflareConnectionPath(pathname) {
26
+ return pathname === '/api/connections/cloudflare'
27
+ || pathname === '/api/connections/cloudflare/start'
28
+ || pathname === CLOUDFLARE_CALLBACK_PATH
29
+ || pathname === '/api/connections/cloudflare/disconnect';
30
+ }
31
+
32
+ async function ensureTables(env) {
33
+ if (!env?.DB?.prepare) return;
34
+ await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_connections (
35
+ connection_id TEXT PRIMARY KEY,
36
+ owner_id TEXT NOT NULL,
37
+ cloudflare_account_id TEXT,
38
+ scopes TEXT,
39
+ status TEXT,
40
+ access_token_encrypted TEXT,
41
+ refresh_token_encrypted TEXT,
42
+ created_at INTEGER,
43
+ updated_at INTEGER,
44
+ expires_at INTEGER
45
+ )`).run();
46
+ await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_oauth_state (
47
+ state TEXT PRIMARY KEY,
48
+ owner_id TEXT NOT NULL,
49
+ code_verifier TEXT NOT NULL,
50
+ created_at INTEGER
51
+ )`).run();
52
+ }
53
+
54
+ export async function handleCloudflareConnectionRequest(request, env) {
55
+ const url = new URL(request.url);
56
+ const client = resolveCloudflareOAuthClient(env);
57
+ if (request.method === 'OPTIONS') {
58
+ return new Response(null, { status: 204 });
59
+ }
60
+
61
+ let body = {};
62
+ if (request.method === 'POST') {
63
+ body = await request.json().catch(() => ({}));
64
+ }
65
+
66
+ const isCallback = url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET';
67
+ let ownerId = '';
68
+ try {
69
+ ownerId = await resolveAuthenticatedOwner(request, env, url, body);
70
+ } catch (err) {
71
+ if (!isCallback || err.code === 'untrusted_owner_hint') {
72
+ const code = err.code || 'unauthenticated';
73
+ return json({ ok: false, error: code }, code === 'untrusted_owner_hint' ? 400 : 401);
74
+ }
75
+ }
76
+
77
+ if (!client.productionReady) {
78
+ if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
79
+ return json({
80
+ ok: true,
81
+ ...cloudflareConnectionSafeStatus(env, null, ownerId),
82
+ });
83
+ }
84
+ if (url.pathname === '/api/connections/cloudflare/start') {
85
+ return json({
86
+ ok: false,
87
+ error: 'not_configured',
88
+ message: 'real Cloudflare OAuth client still required',
89
+ fixture: client.fixture,
90
+ }, 503);
91
+ }
92
+ }
93
+
94
+ await ensureTables(env);
95
+
96
+ if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
97
+ let connection = null;
98
+ if (env.DB) {
99
+ const row = await env.DB.prepare(
100
+ `SELECT connection_id, owner_id, cloudflare_account_id, scopes, status, created_at, updated_at, expires_at
101
+ FROM agentsam_cloudflare_connections WHERE owner_id = ? LIMIT 1`,
102
+ ).bind(ownerId).first();
103
+ if (row) {
104
+ connection = {
105
+ connectionId: row.connection_id,
106
+ ownerId: row.owner_id,
107
+ cloudflareAccountId: row.cloudflare_account_id,
108
+ scopes: row.scopes ? String(row.scopes).split(' ') : [],
109
+ status: row.status,
110
+ createdAt: row.created_at,
111
+ updatedAt: row.updated_at,
112
+ expiresAt: row.expires_at,
113
+ };
114
+ }
115
+ }
116
+ return json({ ok: true, ...cloudflareConnectionSafeStatus(env, connection, ownerId) });
117
+ }
118
+
119
+ if (url.pathname === '/api/connections/cloudflare/start' && request.method === 'GET') {
120
+ const state = crypto.randomUUID().replace(/-/g, '');
121
+ const verifierBytes = crypto.getRandomValues(new Uint8Array(32));
122
+ const verifier = btoa(String.fromCharCode(...verifierBytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
123
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
124
+ const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
125
+ if (env.DB) {
126
+ await env.DB.prepare(
127
+ `INSERT INTO agentsam_cloudflare_oauth_state (state, owner_id, code_verifier, created_at) VALUES (?, ?, ?, unixepoch())`,
128
+ ).bind(state, ownerId, verifier).run();
129
+ } else if (env.oauthState instanceof Map) {
130
+ env.oauthState.set(state, { ownerId, verifier });
131
+ }
132
+ const redirectUri = `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`;
133
+ const authorize = buildAuthorizeUrl({
134
+ clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
135
+ redirectUri,
136
+ state,
137
+ codeChallenge: challenge,
138
+ });
139
+ return json({ ok: true, authorize_url: authorize, callback: CLOUDFLARE_CALLBACK_PATH });
140
+ }
141
+
142
+ if (url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET') {
143
+ const code = url.searchParams.get('code') || '';
144
+ const state = url.searchParams.get('state') || '';
145
+ let stored = null;
146
+ if (env.DB) {
147
+ stored = await env.DB.prepare(
148
+ `SELECT owner_id, code_verifier FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
149
+ ).bind(state).first();
150
+ } else if (env.oauthState instanceof Map) {
151
+ stored = env.oauthState.get(state);
152
+ if (stored) stored = { owner_id: stored.ownerId, code_verifier: stored.verifier };
153
+ }
154
+ if (!stored) {
155
+ return json({ ok: false, error: 'cloudflare_connection_forbidden' }, 403);
156
+ }
157
+ if (!ownerId) ownerId = stored.owner_id;
158
+ if (stored.owner_id !== ownerId) {
159
+ return json({ ok: false, error: 'cloudflare_connection_forbidden' }, 403);
160
+ }
161
+ const tokenRes = await fetch(CLOUDFLARE_OAUTH_TOKEN_URL, {
162
+ method: 'POST',
163
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
164
+ body: new URLSearchParams({
165
+ grant_type: 'authorization_code',
166
+ code,
167
+ redirect_uri: `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`,
168
+ client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
169
+ client_secret: String(env.CLOUDFLARE_OAUTH_CLIENT_SECRET),
170
+ code_verifier: stored.code_verifier,
171
+ }),
172
+ });
173
+ if (!tokenRes.ok) {
174
+ return json({ ok: false, error: 'token_exchange_failed' }, 502);
175
+ }
176
+ const tokens = await tokenRes.json();
177
+ const connectionId = `cfconn_${crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`;
178
+ if (tokens.access_token && !vaultConfigured(env)) {
179
+ return json({ ok: false, error: 'vault_unavailable' }, 503);
180
+ }
181
+ const aad = `cloudflare-connection:${ownerId}`;
182
+ let accessEnc = null;
183
+ let refreshEnc = null;
184
+ try {
185
+ accessEnc = await sealToken(env, tokens.access_token, aad);
186
+ refreshEnc = await sealToken(env, tokens.refresh_token, aad);
187
+ } catch (err) {
188
+ return json({ ok: false, error: err.code || 'vault_unavailable' }, 503);
189
+ }
190
+ if (env.DB) {
191
+ await env.DB.prepare(
192
+ `INSERT INTO agentsam_cloudflare_connections (
193
+ connection_id, owner_id, cloudflare_account_id, scopes, status,
194
+ access_token_encrypted, refresh_token_encrypted, created_at, updated_at, expires_at
195
+ ) VALUES (?, ?, ?, ?, 'connected', ?, ?, unixepoch(), unixepoch(), ?)`,
196
+ ).bind(
197
+ connectionId,
198
+ ownerId,
199
+ tokens.account_id || null,
200
+ (tokens.scope || requestedCloudflareScopes().join(' ')),
201
+ accessEnc,
202
+ refreshEnc,
203
+ tokens.expires_in ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in) : null,
204
+ ).run();
205
+ }
206
+ return redirect(`/agentsam?connection=cloudflare`);
207
+ }
208
+
209
+ if (url.pathname === '/api/connections/cloudflare/disconnect' && request.method === 'POST') {
210
+ if (env.DB) {
211
+ const row = await env.DB.prepare(
212
+ `SELECT connection_id, owner_id FROM agentsam_cloudflare_connections WHERE owner_id = ?`,
213
+ ).bind(ownerId).first();
214
+ if (row) {
215
+ assertConnectionOwner({ ownerId: row.owner_id, connectionId: row.connection_id }, ownerId);
216
+ await env.DB.prepare(`DELETE FROM agentsam_cloudflare_connections WHERE owner_id = ?`).bind(ownerId).run();
217
+ }
218
+ }
219
+ return json({ ok: true, status: 'not_configured' });
220
+ }
221
+
222
+ return json({ ok: false, error: 'not_found' }, 404);
223
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * AES-256-GCM token vault matching apps/local-studio/backend/worker encryptSecret.
3
+ * Connector tokens are server-only. Never return plaintext on status routes.
4
+ */
5
+ const enc = new TextEncoder();
6
+ const dec = new TextDecoder();
7
+
8
+ function b64ToBytes(value) {
9
+ const bin = atob(value);
10
+ const out = new Uint8Array(bin.length);
11
+ for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
12
+ return out;
13
+ }
14
+
15
+ function bytesToB64(bytes) {
16
+ let bin = '';
17
+ for (const b of bytes) bin += String.fromCharCode(b);
18
+ return btoa(bin);
19
+ }
20
+
21
+ export function vaultConfigured(env = {}) {
22
+ return Boolean(env.VAULT_MASTER_KEY || env.VAULT_KEY);
23
+ }
24
+
25
+ async function importVaultKey(env) {
26
+ const raw = env.VAULT_MASTER_KEY || env.VAULT_KEY;
27
+ if (!raw) throw new Error('VAULT_MASTER_KEY missing');
28
+ let keyBytes;
29
+ try {
30
+ keyBytes = b64ToBytes(raw);
31
+ } catch {
32
+ keyBytes = enc.encode(raw);
33
+ }
34
+ if (keyBytes.byteLength === 32) {
35
+ // ok
36
+ } else if (keyBytes.byteLength > 32) {
37
+ keyBytes = keyBytes.slice(0, 32);
38
+ } else {
39
+ const hash = await crypto.subtle.digest('SHA-256', keyBytes);
40
+ keyBytes = new Uint8Array(hash);
41
+ }
42
+ return crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
43
+ }
44
+
45
+ export async function encryptSecret(env, plaintext, aad) {
46
+ const key = await importVaultKey(env);
47
+ const iv = crypto.getRandomValues(new Uint8Array(12));
48
+ const cipher = await crypto.subtle.encrypt(
49
+ { name: 'AES-GCM', iv, additionalData: enc.encode(aad || '') },
50
+ key,
51
+ enc.encode(plaintext),
52
+ );
53
+ const packed = new Uint8Array(iv.byteLength + cipher.byteLength);
54
+ packed.set(iv, 0);
55
+ packed.set(new Uint8Array(cipher), iv.byteLength);
56
+ return bytesToB64(packed);
57
+ }
58
+
59
+ export async function decryptSecret(env, packedB64, aad) {
60
+ const key = await importVaultKey(env);
61
+ const packed = b64ToBytes(packedB64);
62
+ const iv = packed.slice(0, 12);
63
+ const data = packed.slice(12);
64
+ const plain = await crypto.subtle.decrypt(
65
+ { name: 'AES-GCM', iv, additionalData: enc.encode(aad || '') },
66
+ key,
67
+ data,
68
+ );
69
+ return dec.decode(plain);
70
+ }
71
+
72
+ export async function sealToken(env, plaintext, aad) {
73
+ if (!plaintext) return null;
74
+ if (!vaultConfigured(env)) {
75
+ const err = new Error('vault_unavailable');
76
+ err.code = 'vault_unavailable';
77
+ throw err;
78
+ }
79
+ return encryptSecret(env, plaintext, aad);
80
+ }
@@ -0,0 +1,44 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import {
4
+ assertConnectionOwner,
5
+ cloudflareConnectionSafeStatus,
6
+ resolveCloudflareOAuthClient,
7
+ } from '../src/index.js';
8
+
9
+ describe('cloudflare connector', () => {
10
+ it('reports not_configured when credentials are absent', () => {
11
+ const client = resolveCloudflareOAuthClient({});
12
+ assert.equal(client.status, 'not_configured');
13
+ assert.equal(client.configured, false);
14
+ assert.equal(client.productionReady, false);
15
+ });
16
+
17
+ it('loads fake fixtures without treating them as production authority', () => {
18
+ const client = resolveCloudflareOAuthClient({
19
+ CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
20
+ CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
21
+ });
22
+ assert.equal(client.configured, true);
23
+ assert.equal(client.fixture, true);
24
+ assert.equal(client.productionReady, false);
25
+ assert.equal(client.status, 'fixture');
26
+ });
27
+
28
+ it('never includes secrets in safe status output', () => {
29
+ const status = cloudflareConnectionSafeStatus({
30
+ CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
31
+ CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
32
+ });
33
+ const blob = JSON.stringify(status);
34
+ assert.equal(blob.includes('sillynotreal-secret'), false);
35
+ assert.equal(status.secret, 'configured');
36
+ assert.equal(status.configured, false);
37
+ });
38
+
39
+ it('denies cross-account connection access', () => {
40
+ const record = { ownerId: 'sam', connectionId: 'c1' };
41
+ assert.throws(() => assertConnectionOwner(record, 'connor'), /cloudflare_connection_forbidden/);
42
+ assert.equal(assertConnectionOwner(record, 'sam').connectionId, 'c1');
43
+ });
44
+ });
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@inneranimalmedia/agentsam-sdk-identity",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "description": "Identity module for @inneranimalmedia/agentsam-sdk (workspace \u2014 publish via root SDK)",
6
+ "description": "Identity module for @inneranimalmedia/agentsam-sdk (workspace — publish via root SDK)",
7
7
  "agentsam": {
8
8
  "system": "identity",
9
9
  "kind": "library",
@@ -5,20 +5,24 @@
5
5
  * may provide storage/verification adapters without inventing competing aliases.
6
6
  *
7
7
  * Canonical variables:
8
- * IAM_ORIGIN IAM authority + browser/OAuth/API origin
8
+ * IAM_OAUTH_ISSUER public authorization-server authority (issuer)
9
9
  * IAM_CLIENT_ID OAuth client id
10
10
  * IAM_CLIENT_SECRET OAuth client secret
11
+ * IAM_ORIGIN deprecated alias of IAM_OAUTH_ISSUER (migration window)
11
12
  * AGENTSAM_SDK_KEY account/delegated sdk_* bearer
12
13
  * AGENTSAM_BRIDGE_KEY machine/integration credential
13
14
  *
14
15
  * Migration-only fallbacks:
15
- * IAM_OAUTH_ISSUER -> IAM_ORIGIN
16
+ * IAM_ORIGIN -> IAM_OAUTH_ISSUER
16
17
  * AGENTSAM_SDK_TOKEN -> AGENTSAM_SDK_KEY
17
18
  */
18
19
 
19
- export const DEFAULT_IAM_ORIGIN = 'https://inneranimalmedia.com';
20
+ export const DEFAULT_IAM_OAUTH_ISSUER = 'https://inneranimalmedia.com';
21
+ /** @deprecated Use DEFAULT_IAM_OAUTH_ISSUER. */
22
+ export const DEFAULT_IAM_ORIGIN = DEFAULT_IAM_OAUTH_ISSUER;
20
23
 
21
24
  export const AGENTSAM_AUTH_ENV = Object.freeze({
25
+ iamIssuer: 'IAM_OAUTH_ISSUER',
22
26
  iamOrigin: 'IAM_ORIGIN',
23
27
  iamClientId: 'IAM_CLIENT_ID',
24
28
  iamClientSecret: 'IAM_CLIENT_SECRET',
@@ -27,7 +31,7 @@ export const AGENTSAM_AUTH_ENV = Object.freeze({
27
31
  });
28
32
 
29
33
  export const AGENTSAM_AUTH_LEGACY_ENV = Object.freeze({
30
- iamOrigin: 'IAM_OAUTH_ISSUER',
34
+ iamOrigin: 'IAM_ORIGIN',
31
35
  sdkKey: 'AGENTSAM_SDK_TOKEN',
32
36
  });
33
37
 
@@ -35,6 +39,7 @@ export const AGENTSAM_AUTH_CONTRACT = Object.freeze({
35
39
  version: 1,
36
40
  authority: 'agentsam-sdk',
37
41
  iam: Object.freeze({
42
+ issuer: AGENTSAM_AUTH_ENV.iamIssuer,
38
43
  origin: AGENTSAM_AUTH_ENV.iamOrigin,
39
44
  clientId: AGENTSAM_AUTH_ENV.iamClientId,
40
45
  clientSecret: AGENTSAM_AUTH_ENV.iamClientSecret,
@@ -54,6 +59,7 @@ export const AGENTSAM_AUTH_CONTRACT = Object.freeze({
54
59
  }),
55
60
  compatibility: Object.freeze({
56
61
  iamOriginFallback: AGENTSAM_AUTH_LEGACY_ENV.iamOrigin,
62
+ iamIssuerCanonical: AGENTSAM_AUTH_ENV.iamIssuer,
57
63
  sdkKeyFallback: AGENTSAM_AUTH_LEGACY_ENV.sdkKey,
58
64
  }),
59
65
  });
@@ -66,13 +72,18 @@ function normalizeOrigin(value) {
66
72
  return clean(value).replace(/\/+$/, '');
67
73
  }
68
74
 
69
- /** Resolve canonical IAM authority origin with one migration fallback. */
70
- export function resolveIamOrigin(env = {}, explicit = '') {
75
+ /** Resolve canonical IAM issuer. IAM_OAUTH_ISSUER wins; IAM_ORIGIN is compatibility only. */
76
+ export function resolveIamIssuer(env = {}, explicit = '') {
71
77
  return normalizeOrigin(
72
- explicit || env?.IAM_ORIGIN || env?.IAM_OAUTH_ISSUER || DEFAULT_IAM_ORIGIN,
78
+ explicit || env?.IAM_OAUTH_ISSUER || env?.IAM_ORIGIN || DEFAULT_IAM_OAUTH_ISSUER,
73
79
  );
74
80
  }
75
81
 
82
+ /** @deprecated Use resolveIamIssuer. Same resolution order as issuer. */
83
+ export function resolveIamOrigin(env = {}, explicit = '') {
84
+ return resolveIamIssuer(env, explicit);
85
+ }
86
+
76
87
  /** Resolve account/delegated SDK bearer; canonical name wins over legacy. */
77
88
  export function resolveSdkKey(env = {}, explicit = '') {
78
89
  return clean(explicit || env?.AGENTSAM_SDK_KEY || env?.AGENTSAM_SDK_TOKEN);
@@ -9,23 +9,27 @@ import {
9
9
  } from '../src/contracts/auth-config.js';
10
10
 
11
11
  describe('auth configuration contract', () => {
12
- it('prefers canonical IAM_ORIGIN over the migration alias', () => {
12
+ it('prefers canonical IAM_OAUTH_ISSUER over IAM_ORIGIN', () => {
13
13
  assert.equal(
14
14
  resolveIamOrigin({
15
- IAM_ORIGIN: 'https://canonical.example.test/',
16
- IAM_OAUTH_ISSUER: 'https://legacy.example.test/',
15
+ IAM_ORIGIN: 'https://legacy.example.test/',
16
+ IAM_OAUTH_ISSUER: 'https://canonical.example.test/',
17
17
  }),
18
18
  'https://canonical.example.test',
19
19
  );
20
20
  });
21
21
 
22
- it('accepts IAM_OAUTH_ISSUER as migration-only fallback', () => {
22
+ it('accepts IAM_ORIGIN as migration-only fallback', () => {
23
23
  assert.equal(
24
- resolveIamOrigin({ IAM_OAUTH_ISSUER: 'https://legacy.example.test/' }),
24
+ resolveIamOrigin({ IAM_ORIGIN: 'https://legacy.example.test/' }),
25
25
  'https://legacy.example.test',
26
26
  );
27
27
  });
28
28
 
29
+ it('defaults issuer when neither IAM_OAUTH_ISSUER nor IAM_ORIGIN exists', () => {
30
+ assert.equal(resolveIamOrigin({}), 'https://inneranimalmedia.com');
31
+ });
32
+
29
33
  it('prefers AGENTSAM_SDK_KEY over AGENTSAM_SDK_TOKEN', () => {
30
34
  assert.equal(
31
35
  resolveSdkKey({
@@ -46,18 +46,18 @@ describe('oauth credentials', () => {
46
46
  assert.equal(resolveOAuthCredentialLane(env, 'iam')?.lane, 'iam_platform');
47
47
  });
48
48
 
49
- it('prefers IAM_ORIGIN and exposes origin as the canonical authority', () => {
49
+ it('prefers IAM_OAUTH_ISSUER and exposes issuer as the canonical authority', () => {
50
50
  const creds = resolveIamPlatformCredentials({
51
51
  IAM_CLIENT_ID: 'c',
52
52
  IAM_CLIENT_SECRET: 's',
53
- IAM_ORIGIN: 'https://iam.example.test/',
54
- IAM_OAUTH_ISSUER: 'https://legacy.example.test/',
53
+ IAM_ORIGIN: 'https://legacy.example.test/',
54
+ IAM_OAUTH_ISSUER: 'https://iam.example.test/',
55
55
  });
56
56
  assert.equal(creds?.origin, 'https://iam.example.test');
57
57
  assert.equal(creds?.issuer, 'https://iam.example.test');
58
58
  });
59
59
 
60
- it('accepts IAM_OAUTH_ISSUER only as a migration fallback', () => {
60
+ it('accepts IAM_ORIGIN only as a migration fallback', () => {
61
61
  const creds = resolveIamPlatformCredentials({
62
62
  IAM_CLIENT_ID: 'c',
63
63
  IAM_CLIENT_SECRET: 's',
@@ -12,6 +12,7 @@ Platform repositories may consume the SDK, provide host adapters, or incubate pr
12
12
  4. **Contracts travel with the SDK.** Shared schemas and protocol definitions live under `protocol/` and are versioned with the code that consumes them.
13
13
  5. **No copied package trees.** Do not duplicate `protocol/`, `agentsam_sdk/`, or package source under another path in this repository or another repository merely to keep two homes in sync.
14
14
  6. **Repository tools observe before they prescribe.** Generic repository intelligence must accept an explicit repo root, be read-only by default, and must not hardcode one repository's folder names, tenant IDs, workspace IDs, providers, or framework layout.
15
+ 7. **Model facts have one authority chain.** `/models`, CLI JSON, runtime selection, and usage receipts must follow the normative model SSOT contract in `protocol/models/README.md`; provider facts retain authoritative provenance while AgentSam recommendations remain separate policy.
15
16
 
16
17
  ## Layout
17
18
 
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.inneranimalmedia.com/agentsam/capabilities/cloudflare-cpu-audit-input.schema.json",
4
+ "title": "cloudflare.cpu.audit input",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["file"],
8
+ "properties": {
9
+ "cwd": { "type": "string", "minLength": 1 },
10
+ "file": { "type": "string", "minLength": 1 },
11
+ "sources": {
12
+ "type": "array",
13
+ "maxItems": 12,
14
+ "items": { "type": "string", "minLength": 1 }
15
+ },
16
+ "maxFrames": { "type": "integer", "minimum": 1, "maximum": 100 },
17
+ "maxSourceChars": { "type": "integer", "minimum": 1000, "maximum": 64000, "default": 24000 }
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.inneranimalmedia.com/agentsam/capabilities/cloudflare-cpu-profile-input.schema.json",
4
+ "title": "cloudflare.cpu.profile input",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["file"],
8
+ "properties": {
9
+ "cwd": { "type": "string", "minLength": 1 },
10
+ "file": { "type": "string", "minLength": 1 },
11
+ "maxFrames": { "type": "integer", "minimum": 1, "maximum": 100 }
12
+ }
13
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.inneranimalmedia.com/agentsam/capabilities/cloudflare-wrangler-native-input.schema.json",
4
+ "title": "cloudflare.wrangler.native input",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["command"],
8
+ "properties": {
9
+ "command": { "enum": ["whoami", "deployments.list", "versions.list", "types.check", "queues.list"] },
10
+ "cwd": { "type": "string", "minLength": 1 },
11
+ "name": { "type": "string" },
12
+ "account": { "type": "string" },
13
+ "config": { "type": "string" },
14
+ "env": { "type": "string" },
15
+ "profile": { "type": "string" },
16
+ "path": { "type": "string" },
17
+ "page": { "type": "integer", "minimum": 1 }
18
+ }
19
+ }
@@ -279,6 +279,53 @@
279
279
  "side_effects": "none",
280
280
  "output_schema": "protocol/context/resolved-context-pack.schema.json",
281
281
  "status": "experimental"
282
+ },
283
+ "cloudflare.wrangler.native": {
284
+ "id": "cloudflare.wrangler.native",
285
+ "kind": "capability",
286
+ "version": 1,
287
+ "domain": "cloudflare",
288
+ "runtime": "node+wrangler",
289
+ "package": "@inneranimalmedia/agentsam-sdk/cloudflare",
290
+ "export": "runWranglerNative",
291
+ "cli": "agentsam cloudflare run <command>",
292
+ "description": "Execute one allowlisted read-only Wrangler operation with argv-safe cwd/config scoping and structured diagnostics.",
293
+ "deterministic": false,
294
+ "model_required": false,
295
+ "side_effects": "network-read",
296
+ "input_schema": "protocol/capabilities/cloudflare-wrangler-native-input.schema.json",
297
+ "status": "experimental"
298
+ },
299
+ "cloudflare.cpu.profile": {
300
+ "id": "cloudflare.cpu.profile",
301
+ "kind": "capability",
302
+ "version": 1,
303
+ "domain": "cloudflare",
304
+ "runtime": "node",
305
+ "package": "@inneranimalmedia/agentsam-sdk/cloudflare",
306
+ "export": "summarizeCloudflareCpuProfileFile",
307
+ "cli": "agentsam cloudflare cpu analyze <profile.cpuprofile>",
308
+ "description": "Summarize a local workerd/DevTools CPU profile into bounded ranked self-time evidence.",
309
+ "deterministic": true,
310
+ "model_required": false,
311
+ "side_effects": "none",
312
+ "input_schema": "protocol/capabilities/cloudflare-cpu-profile-input.schema.json",
313
+ "status": "experimental"
314
+ },
315
+ "cloudflare.cpu.audit": {
316
+ "id": "cloudflare.cpu.audit",
317
+ "kind": "agent_primitive",
318
+ "version": 1,
319
+ "domain": "cloudflare",
320
+ "runtime": "node",
321
+ "package": "@inneranimalmedia/agentsam-sdk/cloudflare",
322
+ "export": "runCloudflareCpuAudit",
323
+ "description": "Analyze a bounded Cloudflare CPU profile and selected source evidence through an injected reasoning adapter without deploy authority.",
324
+ "deterministic": false,
325
+ "model_required": true,
326
+ "side_effects": "none",
327
+ "input_schema": "protocol/capabilities/cloudflare-cpu-audit-input.schema.json",
328
+ "status": "experimental"
282
329
  }
283
330
  }
284
331
  }
@@ -5,21 +5,10 @@
5
5
  "type": "object",
6
6
  "additionalProperties": false,
7
7
  "required": [
8
- "windowTokens",
9
- "windowChars",
10
- "charsPerToken",
11
- "targetInputRatio",
12
- "hardInputRatio",
13
- "targetInputTokens",
14
- "hardInputTokens",
15
- "targetInputChars",
16
- "hardInputChars",
17
- "maxSystemChars",
18
- "maxToolSchemaChars",
19
- "maxEvidenceChars",
20
- "maxFileCharsPerRead",
21
- "maxFileCharsPerTurn",
22
- "maxToolResultChars"
8
+ "windowTokens", "windowChars", "charsPerToken", "targetInputRatio", "hardInputRatio", "hardWindowRatio",
9
+ "targetInputTokens", "compactAtTokens", "interveneAtTokens", "maxNormalInputTokens", "hardInputTokens",
10
+ "pricingThresholdTokens", "safetyMarginTokens", "targetInputChars", "hardInputChars",
11
+ "maxSystemChars", "maxToolSchemaChars", "maxEvidenceChars", "maxFileCharsPerRead", "maxFileCharsPerTurn", "maxToolResultChars"
23
12
  ],
24
13
  "properties": {
25
14
  "windowTokens": { "type": "integer", "minimum": 1 },
@@ -27,8 +16,14 @@
27
16
  "charsPerToken": { "type": "number", "exclusiveMinimum": 0 },
28
17
  "targetInputRatio": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
29
18
  "hardInputRatio": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
19
+ "hardWindowRatio": { "type": "number", "exclusiveMinimum": 0, "maximum": 1 },
30
20
  "targetInputTokens": { "type": "integer", "minimum": 1 },
21
+ "compactAtTokens": { "type": "integer", "minimum": 1 },
22
+ "interveneAtTokens": { "type": "integer", "minimum": 1 },
23
+ "maxNormalInputTokens": { "type": "integer", "minimum": 1 },
31
24
  "hardInputTokens": { "type": "integer", "minimum": 1 },
25
+ "pricingThresholdTokens": { "type": ["integer", "null"], "minimum": 1 },
26
+ "safetyMarginTokens": { "type": "integer", "minimum": 0 },
32
27
  "targetInputChars": { "type": "integer", "minimum": 1 },
33
28
  "hardInputChars": { "type": "integer", "minimum": 1 },
34
29
  "maxSystemChars": { "type": "integer", "minimum": 1 },