@inneranimalmedia/agentsam-sdk 2.4.1 → 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 +17 -12
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/AGENTSAM_WORKBENCH.md +30 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
- package/docs/CAPABILITIES.md +5 -3
- package/docs/CLI_SHELL.md +167 -80
- package/docs/CMS_STUDIO.md +101 -0
- package/docs/CONTEXT.md +170 -0
- package/docs/LOCAL_OLLAMA.md +58 -0
- package/docs/MERKLE.md +19 -19
- package/docs/PORTABLE_CONTEXT.md +4 -3
- package/docs/PROJECT_CONFIG.md +72 -0
- package/docs/RELEASES.md +35 -2
- package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
- package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
- package/docs/SDK_WORKER.md +86 -0
- package/docs/SECURITY.md +59 -22
- package/docs/client-cms-editor.md +15 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
- package/package.json +35 -10
- 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 +10 -1
- package/packages/identity/src/contracts/auth-config.js +109 -0
- package/packages/identity/src/index.js +1 -0
- package/packages/identity/src/oauth/README.md +2 -2
- package/packages/identity/src/oauth/credentials.js +11 -4
- package/packages/identity/src/oauth/iam-platform.js +3 -3
- package/packages/identity/src/providers/iam/index.js +7 -7
- package/packages/identity/src/providers/iam/oauth.js +6 -4
- package/packages/identity/src/providers/iam/profile.js +5 -5
- package/packages/identity/tests/auth-config.test.mjs +61 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
- package/protocol/FILEMETA_V1.md +95 -0
- package/protocol/INSPECT_VIEWS_V1.md +40 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
- package/protocol/MERKLE_V1.md +3 -1
- 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 +79 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +36 -0
- package/protocol/context/context-item.schema.json +19 -0
- package/protocol/context/resolved-context-pack.schema.json +47 -0
- package/protocol/context/result-policy.schema.json +17 -0
- package/protocol/knowledge/context-pack.schema.json +33 -0
- package/protocol/knowledge/retrieval-query.schema.json +68 -13
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/python/README.md +15 -7
- package/python/agentsam_sdk/cli.py +0 -21
- package/python/agentsam_sdk/tui/README.md +17 -12
- package/python/agentsam_sdk/tui/bootstrap.py +2 -2
- package/python/agentsam_sdk/tui/demo.py +25 -10
- package/python/agentsam_sdk/tui/onboarding.py +208 -0
- package/python/tests/test_tui_cli.py +7 -6
- package/skills/README.md +21 -0
- package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
- package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
- package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -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/agentsam-jr-dev/SKILL.md +232 -0
- package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
- package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
- package/skills/agentsam-progression-guard/SKILL.md +197 -0
- package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
- package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
- package/skills/catalog.json +71 -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/capabilities/index.js +7 -0
- package/src/capabilities/repository-snapshot-view.js +238 -0
- package/src/capabilities/repository-snapshot.js +28 -14
- package/src/cli.js +198 -76
- 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/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +54 -32
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +18 -0
- package/src/commands/knowledge.js +27 -4
- package/src/commands/merkle-persist.js +118 -0
- package/src/commands/merkle.js +32 -17
- package/src/commands/models.js +152 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +144 -0
- package/src/commands/product.js +86 -16
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +8 -6
- package/src/commands/shell.js +405 -100
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +120 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +7 -0
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +116 -0
- package/src/context/result-policy.js +66 -0
- 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 +70 -0
- package/src/indexing/execution-boundary.js +144 -0
- package/src/indexing/index.js +8 -0
- package/src/indexing/provider.js +41 -0
- package/src/knowledge/config.js +2 -2
- package/src/knowledge/context-pack.js +12 -1
- package/src/knowledge/contracts.js +9 -4
- package/src/knowledge/engine.js +7 -3
- package/src/knowledge/service/server.js +1 -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/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +78 -0
- package/src/lib/core-client.js +8 -8
- 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/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/local-status.js +9 -17
- package/src/lib/merkle/cloudflare-persistence.js +321 -0
- package/src/lib/merkle/filemeta.js +43 -0
- package/src/lib/merkle/git-ignore.js +24 -0
- package/src/lib/merkle/hash.js +1 -0
- package/src/lib/merkle/index.js +22 -0
- package/src/lib/merkle/persistence.js +72 -0
- package/src/lib/merkle/semantic.js +359 -0
- package/src/lib/merkle/snapshot.js +9 -3
- package/src/lib/merkle/tree.js +11 -6
- package/src/lib/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/save-sdk-token.js +1 -1
- 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 -15
- package/src/lib/tools.js +11 -5
- 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/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/process.js +35 -9
- package/src/security/render.js +27 -5
- package/src/security/scan.js +24 -9
- package/src/security/trust-boundary.js +24 -0
- package/src/skills/index.js +64 -0
- 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 +2 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +54 -0
- package/src/ui/merkle/render.js +1 -0
- package/src/ui/runtime-activity.js +192 -0
- package/src/ui/theme.js +19 -18
- package/test/account-session.test.mjs +36 -0
- package/test/app-building-skills.test.mjs +61 -0
- package/test/apps-scaffold-contract.test.mjs +56 -0
- package/test/capabilities.test.mjs +53 -4
- package/test/cli-preferences.test.mjs +46 -0
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +144 -0
- 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/indexing-provider.test.mjs +29 -0
- package/test/jr-dev-skill.test.mjs +26 -0
- package/test/knowledge-context-pack.test.mjs +19 -0
- package/test/knowledge.test.mjs +1 -1
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +51 -0
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +94 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/responses-runner.test.mjs +148 -0
- package/test/runtime-activity.test.mjs +98 -0
- package/test/sdk-worker-contract.test.mjs +68 -0
- package/test/security.test.mjs +46 -0
- package/test/shell.test.mjs +55 -21
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +5 -2
- package/test/telemetry.test.mjs +79 -0
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +40 -0
- package/test/whoami-resume.test.mjs +56 -0
- package/examples/agentsam-tui-ansi.mjs +0 -149
- package/src/commands/tui.js +0 -120
- package/src/ui/splash-xterm.js +0 -290
- package/src/ui/splash.js +0 -426
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
export const PROJECT_RULES_FILENAME = '.agentsamrules';
|
|
6
|
+
export const DEFAULT_PROJECT_RULES_MAX_CHARS = 24_000;
|
|
7
|
+
|
|
8
|
+
function sha256(value) {
|
|
9
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function defaultProjectRules(projectName = 'this project') {
|
|
13
|
+
return `# Agent Sam project rules
|
|
14
|
+
|
|
15
|
+
These instructions are committed with ${projectName} and apply when Agent Sam works in this repository.
|
|
16
|
+
|
|
17
|
+
- Prefer repository evidence over assumptions.
|
|
18
|
+
- Prefer structural lookup before semantic retrieval when an exact symbol or relationship can answer the question.
|
|
19
|
+
- Keep retrieval bounded: metadata -> cards -> excerpt -> range -> full object only when required.
|
|
20
|
+
- Keep secrets, account/runtime state, active runs, and machine-local preferences out of committed project configuration.
|
|
21
|
+
- Treat Git as source-control authority and Merkle snapshots as filesystem evidence.
|
|
22
|
+
- Run the smallest relevant validation before reporting a code change as complete.
|
|
23
|
+
|
|
24
|
+
## Project-specific rules
|
|
25
|
+
|
|
26
|
+
Add repository-specific conventions, commands, constraints, or architecture notes below this line.
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ensureProjectRules(root, projectName = path.basename(path.resolve(root))) {
|
|
31
|
+
const filename = path.join(path.resolve(root), PROJECT_RULES_FILENAME);
|
|
32
|
+
if (fs.existsSync(filename)) return filename;
|
|
33
|
+
fs.writeFileSync(filename, defaultProjectRules(projectName), { encoding: 'utf8', flag: 'wx' });
|
|
34
|
+
return filename;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findProjectRules(startDir = process.cwd()) {
|
|
38
|
+
let dir = path.resolve(startDir);
|
|
39
|
+
for (let i = 0; i < 16; i += 1) {
|
|
40
|
+
const filename = path.join(dir, PROJECT_RULES_FILENAME);
|
|
41
|
+
if (fs.existsSync(filename)) return filename;
|
|
42
|
+
const projectBoundary = fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, '.agentsam', 'config.json'));
|
|
43
|
+
if (projectBoundary) break;
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent === dir) break;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function loadProjectRules(startDir = process.cwd(), options = {}) {
|
|
52
|
+
const maxChars = Number(options.maxChars ?? DEFAULT_PROJECT_RULES_MAX_CHARS);
|
|
53
|
+
if (!Number.isInteger(maxChars) || maxChars < 1) throw new RangeError('project rules maxChars must be a positive integer');
|
|
54
|
+
const filename = options.filename ? path.resolve(options.filename) : findProjectRules(startDir);
|
|
55
|
+
if (!filename) return Object.freeze({ found: false, path: null, content: '', chars: 0, truncated: false, hash: null });
|
|
56
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
57
|
+
const truncated = source.length > maxChars;
|
|
58
|
+
const content = truncated ? `${source.slice(0, Math.max(0, maxChars - 1))}…` : source;
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
found: true,
|
|
61
|
+
path: filename,
|
|
62
|
+
content,
|
|
63
|
+
chars: content.length,
|
|
64
|
+
source_chars: source.length,
|
|
65
|
+
truncated,
|
|
66
|
+
hash: sha256(source),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -11,7 +11,7 @@ export function saveSdkTokenHint(cwd, token) {
|
|
|
11
11
|
fs.mkdirSync(dir, { recursive: true });
|
|
12
12
|
const hintPath = path.join(dir, 'credentials.env.example');
|
|
13
13
|
const content = `# Agent Sam SDK — add to your shell profile (optional)
|
|
14
|
-
export
|
|
14
|
+
export AGENTSAM_SDK_KEY=${t}
|
|
15
15
|
export USER_GCP_PROJECT=YOUR_GCP_PROJECT_ID
|
|
16
16
|
`;
|
|
17
17
|
fs.writeFileSync(hintPath, content, 'utf8');
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
[
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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,34 +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: '/
|
|
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: '/
|
|
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: '
|
|
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' },
|
|
25
|
+
{ cmd: '/agent', description: 'Send a goal to the configured local Agent Sam runtime', lane: 'agent' },
|
|
18
26
|
{ cmd: '/logs', description: 'Show local Agent Sam execution events', lane: 'observability' },
|
|
19
|
-
{ cmd: '/tui', description: 'Switch or preview terminal presentation', lane: 'terminal' },
|
|
20
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' },
|
|
21
30
|
{ cmd: '/exit', description: 'Exit Agent Sam shell and return to the host terminal' },
|
|
22
31
|
];
|
|
23
32
|
|
|
24
|
-
/** Shell UX rollout phases (gorilla-shell → SDK default CLI experience). */
|
|
25
33
|
export const SHELL_PHASES = [
|
|
26
34
|
{ id: '0-prototype', label: 'Visual prototype + demo scenarios', status: 'complete' },
|
|
27
|
-
{ id: 'pty-connection', label: 'Local PTY via agentsam start-local', status: '
|
|
28
|
-
{ id: '
|
|
29
|
-
{ id: '
|
|
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' },
|
|
30
38
|
{ id: 'dashboard-embed', label: 'Embeddable shell for IAM dashboard', status: 'planned' },
|
|
31
|
-
{ id: 'standalone-pwa', label: 'Installable PWA / SDK default shell', status: 'planned' },
|
|
32
39
|
];
|
|
33
40
|
|
|
34
41
|
export function listSlashCommands(opts = {}) {
|
package/src/lib/tools.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_RESULT_POLICY } from '../context/result-policy.js';
|
|
2
|
+
|
|
1
3
|
const CATALOG = {
|
|
2
4
|
fullstack: [
|
|
3
5
|
{ name: 'plan', description: 'Break a goal into implementation steps.' },
|
|
@@ -30,9 +32,13 @@ export function normalizeLane(lane = 'fullstack') {
|
|
|
30
32
|
|
|
31
33
|
export function getToolCatalog(lane = 'fullstack') {
|
|
32
34
|
const key = normalizeLane(lane);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const rows = key.includes('cms') ? CATALOG.cms
|
|
36
|
+
: key.includes('data') ? CATALOG.data
|
|
37
|
+
: (key.includes('customer') || key.includes('crm')) ? CATALOG.crm
|
|
38
|
+
: (key.includes('creative') || key.includes('design')) ? CATALOG.creative
|
|
39
|
+
: CATALOG.fullstack;
|
|
40
|
+
return rows.map((tool) => ({
|
|
41
|
+
...tool,
|
|
42
|
+
result_policy: { ...DEFAULT_RESULT_POLICY },
|
|
43
|
+
}));
|
|
38
44
|
}
|
|
@@ -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
|
+
}
|