@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTSAM.md +55 -0
- package/README.md +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/RELEASES.md +16 -7
- package/package.json +20 -8
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/catalog.json +18 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -0
- package/src/cli.js +98 -28
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/deploy.js +39 -3
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/models.js +85 -40
- package/src/commands/preferences.js +101 -59
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +370 -109
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +55 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/cli-preferences.js +28 -24
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -16
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/process.js +35 -9
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +1 -0
- package/src/ui/boot.js +15 -17
- package/test/account-session.test.mjs +36 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +30 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +4 -4
- package/test/responses-runner.test.mjs +148 -0
- package/test/shell.test.mjs +47 -20
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentsam-cloudflare-workers
|
|
3
|
+
description: >
|
|
4
|
+
Treat Cloudflare Workers, Wrangler, Durable Objects, D1, R2, Queues, Hyperdrive,
|
|
5
|
+
Browser, Containers, observability, versions, and deployment mechanics as a native
|
|
6
|
+
AgentSam execution dialect. Use when inspecting, debugging, profiling, operating,
|
|
7
|
+
or shipping Cloudflare-backed applications. Prefer typed Wrangler operations,
|
|
8
|
+
machine-readable output, explicit risk classes, real error/trace identifiers, and
|
|
9
|
+
Worker-runtime semantics over generic shell guessing.
|
|
10
|
+
metadata:
|
|
11
|
+
short-description: "Cloudflare Workers/Wrangler native operations, observability, errors, and CPU discipline"
|
|
12
|
+
aliases:
|
|
13
|
+
- cloudflare
|
|
14
|
+
- workers
|
|
15
|
+
- wrangler
|
|
16
|
+
- cf-native
|
|
17
|
+
user-invocable: true
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# AgentSam Cloudflare Workers
|
|
21
|
+
|
|
22
|
+
Cloudflare is a first-class execution environment, not just a deploy target. Use its
|
|
23
|
+
runtime rules and Wrangler command contracts as native vocabulary.
|
|
24
|
+
|
|
25
|
+
## Operating laws
|
|
26
|
+
|
|
27
|
+
- Prefer structured Wrangler output such as `whoami --json`, deployments/versions JSON, and JSON tail events where supported.
|
|
28
|
+
- Never ask a model to read or print `wrangler auth token`, API tokens, secrets, cookies, or credential files. Identity status is useful evidence; credential bytes are not.
|
|
29
|
+
- Classify every operation before execution: read, local-runtime, filesystem-write, remote-write, secret-bearing, or long-running stream.
|
|
30
|
+
- Keep `cwd`, account authority, connection identity, and execution leases runtime-owned. Do not accept them from model arguments when they are security-relevant.
|
|
31
|
+
- Preserve real provider/runtime errors: process exit code, HTTP status, machine error type/code, request/Ray IDs, retry metadata, and bounded redacted stderr/body.
|
|
32
|
+
- Do not infer CPU time from `performance.now()` or `Date.now()` around pure computation in a deployed Worker. Production timers advance around I/O; use local workerd/DevTools CPU profiles and production CPU metrics.
|
|
33
|
+
- For expensive CPU investigations, collect a bounded profile summary and selected source evidence first, then hand that packet to the chosen reasoning model. Do not dump the repository or raw multi-megabyte profile into model context.
|
|
34
|
+
- Prefer Web Crypto/native runtime primitives for CPU-intensive cryptography instead of pure-JavaScript reimplementations where the Worker contract allows it.
|
|
35
|
+
- Remember that open TCP sockets have runtime and Durable Object lifecycle/cost implications; close them deliberately and prefer platform-native database connectivity such as Hyperdrive where appropriate.
|
|
36
|
+
|
|
37
|
+
## Native workflow
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
identify worker/config
|
|
41
|
+
-> verify Cloudflare identity without exposing token
|
|
42
|
+
-> inspect deployments/versions/config/types
|
|
43
|
+
-> reproduce locally with wrangler dev/workerd
|
|
44
|
+
-> collect JSON logs / CPU profile / production metrics
|
|
45
|
+
-> normalize errors and trace IDs
|
|
46
|
+
-> select only implicated source evidence
|
|
47
|
+
-> deterministic summary
|
|
48
|
+
-> optional high-reasoning model audit
|
|
49
|
+
-> verify locally
|
|
50
|
+
-> deploy only through the normal progression guard
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Read the references for command risk classes, error envelopes, and CPU/observability mechanics.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Worker CPU profiling
|
|
2
|
+
|
|
3
|
+
A deployed Worker's `performance.now()` and `Date.now()` do not measure pure CPU loops reliably because the clock advances around I/O rather than ordinary CPU execution. Never create fake production CPU timings from those APIs.
|
|
4
|
+
|
|
5
|
+
For CPU work:
|
|
6
|
+
|
|
7
|
+
1. Reproduce with `wrangler dev`/workerd using production-like routes, request volume, and data/bindings where safe.
|
|
8
|
+
2. Open DevTools from the Wrangler session and record a CPU profile.
|
|
9
|
+
3. Export the `.cpuprofile` and run `agentsam cloudflare cpu analyze <profile.cpuprofile>`.
|
|
10
|
+
4. Use the ranked self-time frames to select a small source slice.
|
|
11
|
+
5. Optionally hand the bounded profile + selected source packet to a high-reasoning model through `cloudflare.cpu.audit`.
|
|
12
|
+
6. Verify the fix locally, then compare production CPU metrics/error rate after normal deployment gates.
|
|
13
|
+
|
|
14
|
+
Pay attention to garbage collection as well as application frames. Large allocation churn can be the hotspot even when no single application function looks dominant.
|
|
15
|
+
|
|
16
|
+
Use native implementations when they remove JavaScript CPU work. For example, Worker Web Crypto operations are preferable to CPU-heavy pure-JavaScript cryptography when compatible with the required algorithm and contract.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Cloudflare errors and observability
|
|
2
|
+
|
|
3
|
+
## Error evidence contract
|
|
4
|
+
|
|
5
|
+
When an operation fails, retain the most specific machine evidence available:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
source/provider
|
|
9
|
+
operation
|
|
10
|
+
process exit code or HTTP status
|
|
11
|
+
machine error type
|
|
12
|
+
machine error code
|
|
13
|
+
request id / cf-ray when present
|
|
14
|
+
retry-after and retry classification
|
|
15
|
+
requested vs resolved processing/runtime mode when relevant
|
|
16
|
+
bounded redacted response body or stderr
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Do not flatten this to `request failed`, `429`, `500`, or `Wrangler failed` when richer evidence exists.
|
|
20
|
+
|
|
21
|
+
## Retry discipline
|
|
22
|
+
|
|
23
|
+
Retry only failures that can plausibly recover without changing operator state. Rate limits, ramp-rate throttles, overload, and transient provider 5xx errors may be retryable. Authentication, invalid arguments, billing/spend limits, unsupported regions, and missing permissions require configuration or operator action instead of retry loops.
|
|
24
|
+
|
|
25
|
+
## Worker telemetry
|
|
26
|
+
|
|
27
|
+
Use production Workers metrics/logs/traces for live evidence. Tail Workers and diagnostics channels can carry structured diagnostic events, but observability itself consumes resources and should remain bounded. Prefer structured fields and stable trace/request IDs over giant free-form logs.
|
|
28
|
+
|
|
29
|
+
Unhandled promise rejections are runtime evidence and should be surfaced with the rejection reason/trace while redacting secrets.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Wrangler native map
|
|
2
|
+
|
|
3
|
+
Treat Wrangler commands as typed operations rather than arbitrary shell text.
|
|
4
|
+
|
|
5
|
+
## Safe model-visible reads
|
|
6
|
+
|
|
7
|
+
AgentSam's initial native executor intentionally exposes only:
|
|
8
|
+
|
|
9
|
+
- `whoami --json`
|
|
10
|
+
- `deployments list --json`
|
|
11
|
+
- `versions list --json`
|
|
12
|
+
- `types --check`
|
|
13
|
+
- `queues list`
|
|
14
|
+
|
|
15
|
+
These are argv-built and cwd/config scoped. The allowlist is intentionally smaller than Wrangler's full command surface.
|
|
16
|
+
|
|
17
|
+
## Known operational families
|
|
18
|
+
|
|
19
|
+
- Identity/config: `whoami`, `auth list`, `auth activate`, `login`.
|
|
20
|
+
- Development: `dev`, `types`, local persistence, remote bindings.
|
|
21
|
+
- Observability: `tail`, deployments, versions, logs/traces/metrics.
|
|
22
|
+
- Delivery: `deploy`, version deployment, rollback, triggers.
|
|
23
|
+
- Data: D1, R2, KV, Queues, Hyperdrive, Vectorize.
|
|
24
|
+
- Compute/AI: Containers, Browser, Workers AI, Workflows and related products.
|
|
25
|
+
|
|
26
|
+
Remote mutation, rollback, secret handling, token retrieval, data writes, and long-running streams require a dedicated policy/approval path. Do not broaden the safe executor by passing arbitrary trailing argv.
|
|
27
|
+
|
|
28
|
+
Wrangler global controls such as `--config`, `--cwd`, `--env`, and `--profile` are useful scoping mechanics. Keep them explicit; do not silently switch accounts or environments.
|
package/skills/catalog.json
CHANGED
|
@@ -48,6 +48,24 @@
|
|
|
48
48
|
"agentsam-progression-guard/references/hooks-operational-io.md"
|
|
49
49
|
],
|
|
50
50
|
"user_invocable": true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "agentsam-cloudflare-workers",
|
|
54
|
+
"aliases": [
|
|
55
|
+
"cloudflare",
|
|
56
|
+
"workers",
|
|
57
|
+
"wrangler",
|
|
58
|
+
"cf-native"
|
|
59
|
+
],
|
|
60
|
+
"title": "AgentSam Cloudflare Workers",
|
|
61
|
+
"description": "Operate and debug Cloudflare Workers with typed Wrangler operations, runtime-aware observability, structured errors, and CPU profiling discipline.",
|
|
62
|
+
"entry": "agentsam-cloudflare-workers/SKILL.md",
|
|
63
|
+
"references": [
|
|
64
|
+
"agentsam-cloudflare-workers/references/wrangler-native-map.md",
|
|
65
|
+
"agentsam-cloudflare-workers/references/errors-and-observability.md",
|
|
66
|
+
"agentsam-cloudflare-workers/references/cpu-profiling.md"
|
|
67
|
+
],
|
|
68
|
+
"user_invocable": true
|
|
51
69
|
}
|
|
52
70
|
]
|
|
53
71
|
}
|
|
@@ -1,14 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
1
4
|
import { getCapability, listCapabilities } from '../capabilities/manifest.js';
|
|
2
5
|
import { repositorySnapshot } from '../capabilities/repository-snapshot.js';
|
|
6
|
+
import { runWranglerNative, summarizeCloudflareCpuProfileFile, runCloudflareCpuAudit } from '../cloudflare/index.js';
|
|
3
7
|
import { runRepositoryAudit } from './repository-audit.js';
|
|
4
8
|
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL('../../', import.meta.url)));
|
|
10
|
+
|
|
11
|
+
function loadSchema(value) {
|
|
12
|
+
if (!value) return { type: 'object', properties: {} };
|
|
13
|
+
if (typeof value === 'object' && !Array.isArray(value)) return structuredClone(value);
|
|
14
|
+
const filename = path.resolve(PACKAGE_ROOT, String(value));
|
|
15
|
+
if (filename !== PACKAGE_ROOT && !filename.startsWith(`${PACKAGE_ROOT}${path.sep}`)) throw new Error(`capability_schema_outside_package:${value}`);
|
|
16
|
+
try { return JSON.parse(fs.readFileSync(filename, 'utf8')); }
|
|
17
|
+
catch (error) { throw new Error(`capability_schema_unreadable:${value}:${error?.message || error}`); }
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
export function createCapabilityAdapter({ handlers = {}, reasoner } = {}) {
|
|
6
21
|
const executable = new Map([
|
|
7
22
|
['repository.snapshot', (input) => repositorySnapshot(input)],
|
|
23
|
+
['cloudflare.wrangler.native', (input = {}) => runWranglerNative(input.command, input)],
|
|
24
|
+
['cloudflare.cpu.profile', (input = {}) => summarizeCloudflareCpuProfileFile(input)],
|
|
8
25
|
...Object.entries(handlers),
|
|
9
26
|
]);
|
|
10
27
|
if (typeof reasoner === 'function') {
|
|
11
28
|
executable.set('repository.audit', (input = {}) => runRepositoryAudit({ ...input, reasoner }));
|
|
29
|
+
executable.set('cloudflare.cpu.audit', (input = {}) => runCloudflareCpuAudit({ ...input, reasoner }));
|
|
12
30
|
}
|
|
13
31
|
|
|
14
32
|
function describe(id) {
|
|
@@ -18,33 +36,27 @@ export function createCapabilityAdapter({ handlers = {}, reasoner } = {}) {
|
|
|
18
36
|
}
|
|
19
37
|
|
|
20
38
|
return Object.freeze({
|
|
21
|
-
list(options = {}) {
|
|
22
|
-
return listCapabilities(options);
|
|
23
|
-
},
|
|
39
|
+
list(options = {}) { return listCapabilities(options); },
|
|
24
40
|
describe,
|
|
25
41
|
toolDescriptors({ domain, kind, includeUnavailable = false } = {}) {
|
|
26
|
-
return listCapabilities({ domain, kind }).filter((row) => includeUnavailable || executable.has(row.id)).map((row) => ({
|
|
42
|
+
return listCapabilities({ domain, kind, status: null }).filter((row) => includeUnavailable || executable.has(row.id)).map((row) => ({
|
|
27
43
|
name: row.id,
|
|
28
44
|
description: row.description,
|
|
29
|
-
|
|
45
|
+
category: row.domain,
|
|
46
|
+
risk: row.side_effects === 'none' || row.side_effects === 'network-read' ? 'read' : 'write',
|
|
47
|
+
input_schema: loadSchema(row.input_schema),
|
|
30
48
|
side_effects: row.side_effects,
|
|
31
49
|
deterministic: row.deterministic,
|
|
32
50
|
model_required: row.model_required,
|
|
33
51
|
}));
|
|
34
52
|
},
|
|
35
|
-
canInvoke(id) {
|
|
36
|
-
return executable.has(String(id || '').trim());
|
|
37
|
-
},
|
|
53
|
+
canInvoke(id) { return executable.has(String(id || '').trim()); },
|
|
38
54
|
async invoke(id, input = {}) {
|
|
39
55
|
const capability = describe(id);
|
|
40
56
|
const handler = executable.get(capability.id);
|
|
41
57
|
if (!handler) throw new Error(`capability_handler_unavailable:${capability.id}`);
|
|
42
58
|
const value = await handler(input);
|
|
43
|
-
return {
|
|
44
|
-
capability_id: capability.id,
|
|
45
|
-
capability_version: capability.version,
|
|
46
|
-
result: value,
|
|
47
|
-
};
|
|
59
|
+
return { capability_id: capability.id, capability_version: capability.version, result: value };
|
|
48
60
|
},
|
|
49
61
|
});
|
|
50
62
|
}
|
package/src/agent/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { createCapabilityAdapter } from './capability-adapter.js';
|
|
2
2
|
export { buildRepositoryAuditPacket, renderRepositoryAuditMarkdown, runRepositoryAudit } from './repository-audit.js';
|
|
3
|
+
export { buildAgentToolSurface, capabilityFunctionName, runResponsesAgent } from './responses-runner.js';
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { assessContextUsage, compileAgentInstructions, createContextBudget, estimateContextTokens, truncateResultText } from '../context/index.js';
|
|
4
|
+
import { getModelRecord, calculateModelCost } from '../models/index.js';
|
|
5
|
+
import { searchToolCards, hydrateToolSchemas } from '../tools/index.js';
|
|
6
|
+
import { createAgentEvent } from '../telemetry/index.js';
|
|
7
|
+
import { diagnosticFromError } from '../errors/index.js';
|
|
8
|
+
|
|
9
|
+
const RUNTIME_OWNED_KEYS = new Set(['account_id', 'user_id', 'tenant_id', 'workspace_id', 'connection_id', 'runtime_lease_id', 'execution_id']);
|
|
10
|
+
|
|
11
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
12
|
+
function hash(value) { return `sha256:${createHash('sha256').update(String(value)).digest('hex')}`; }
|
|
13
|
+
function event(emit, type, payload, runId) { if (typeof emit === 'function') emit(createAgentEvent(type, payload, { runId })); }
|
|
14
|
+
|
|
15
|
+
export function capabilityFunctionName(id) {
|
|
16
|
+
const source = clean(id).replace(/[^A-Za-z0-9_-]+/g, '_').replace(/^_+|_+$/g, '') || 'capability';
|
|
17
|
+
if (source.length <= 58) return `as_${source}`;
|
|
18
|
+
return `as_${source.slice(0, 45)}_${createHash('sha256').update(source).digest('hex').slice(0, 10)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function userMessage(text) {
|
|
22
|
+
return { role: 'user', content: [{ type: 'input_text', text: String(text) }] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function modelBudget(record) {
|
|
26
|
+
const policy = record.context_policy || {};
|
|
27
|
+
return createContextBudget({
|
|
28
|
+
windowTokens: record.context_window,
|
|
29
|
+
targetInputTokens: policy.target_input_tokens,
|
|
30
|
+
compactAtTokens: policy.compact_at_tokens,
|
|
31
|
+
interveneAtTokens: policy.intervene_at_tokens,
|
|
32
|
+
maxNormalInputTokens: policy.max_normal_input_tokens,
|
|
33
|
+
pricingThresholdTokens: policy.pricing_threshold_tokens,
|
|
34
|
+
safetyMarginTokens: policy.safety_margin_tokens,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildAgentToolSurface(capabilityAdapter, objective, options = {}) {
|
|
39
|
+
if (!capabilityAdapter?.toolDescriptors) throw new TypeError('capabilityAdapter.toolDescriptors is required');
|
|
40
|
+
const catalog = capabilityAdapter.toolDescriptors({ includeUnavailable: false });
|
|
41
|
+
const searched = searchToolCards(catalog, objective, { maxItems: options.maxTools ?? 8, maxChars: options.maxCardChars ?? 24_000 });
|
|
42
|
+
const selected = searched.cards.map((card) => card.tool);
|
|
43
|
+
const hydrated = hydrateToolSchemas(catalog, selected, { maxTools: options.maxTools ?? 8, maxChars: options.maxSchemaChars ?? 40_000 });
|
|
44
|
+
const aliases = new Map();
|
|
45
|
+
const tools = hydrated.tools.map((descriptor) => {
|
|
46
|
+
const alias = capabilityFunctionName(descriptor.name);
|
|
47
|
+
aliases.set(alias, descriptor.name);
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
type: 'function',
|
|
50
|
+
name: alias,
|
|
51
|
+
description: descriptor.description,
|
|
52
|
+
parameters: descriptor.input_schema || { type: 'object', properties: {} },
|
|
53
|
+
strict: true,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
tools: Object.freeze(tools),
|
|
58
|
+
aliases,
|
|
59
|
+
descriptors: Object.freeze(hydrated.tools),
|
|
60
|
+
receipt: Object.freeze({
|
|
61
|
+
catalog_tools: catalog.length,
|
|
62
|
+
cards_returned: searched.receipt.returned_items,
|
|
63
|
+
card_chars: searched.receipt.chars,
|
|
64
|
+
hydrated_tools: hydrated.receipt.hydrated_tools,
|
|
65
|
+
hydrated_schema_chars: hydrated.receipt.schema_chars,
|
|
66
|
+
deferred_tools: Object.freeze(hydrated.receipt.deferred_tools),
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sanitizeToolInput(value, descriptor, cwd) {
|
|
72
|
+
const parsed = value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};
|
|
73
|
+
for (const key of RUNTIME_OWNED_KEYS) delete parsed[key];
|
|
74
|
+
const properties = descriptor?.input_schema?.properties || {};
|
|
75
|
+
if (Object.hasOwn(properties, 'cwd')) parsed.cwd = cwd;
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseArguments(value) {
|
|
80
|
+
if (!clean(value)) return {};
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(value);
|
|
83
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('arguments must be an object');
|
|
84
|
+
return parsed;
|
|
85
|
+
} catch (error) { throw new Error(`invalid_tool_arguments:${error?.message || error}`); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function boundedToolOutput(value, callId, maxChars) {
|
|
89
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
90
|
+
const ref = `tool:${callId}`;
|
|
91
|
+
const digest = hash(serialized);
|
|
92
|
+
if (serialized.length <= maxChars) return Object.freeze({ ref, hash: digest, source_chars: serialized.length, chars: serialized.length, truncated: false, output: serialized });
|
|
93
|
+
const bounded = truncateResultText(serialized, maxChars);
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
ref,
|
|
96
|
+
hash: digest,
|
|
97
|
+
source_chars: serialized.length,
|
|
98
|
+
chars: bounded.chars,
|
|
99
|
+
truncated: true,
|
|
100
|
+
output: JSON.stringify({ ref, hash: digest, truncated: true, source_chars: serialized.length, excerpt: bounded.text }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toolResultCharBudget(activeTokens, budget) {
|
|
105
|
+
if (!Number.isFinite(activeTokens) || activeTokens < 0) return budget.maxToolResultChars;
|
|
106
|
+
const reserveTokens = 4_000;
|
|
107
|
+
const headroomTokens = Math.max(256, budget.maxNormalInputTokens - Math.ceil(activeTokens) - reserveTokens);
|
|
108
|
+
return Math.max(1_024, Math.min(budget.maxToolResultChars, Math.floor(headroomTokens * budget.charsPerToken)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget }) {
|
|
112
|
+
const inputChars = typeof input === 'string' ? input.length : JSON.stringify(input ?? '').length;
|
|
113
|
+
const newTokens = estimateContextTokens(String(instructions || '').length + inputChars + toolSurface.receipt.hydrated_schema_chars, budget.charsPerToken);
|
|
114
|
+
return Math.max(newTokens, Number.isFinite(priorActiveTokens) ? Math.ceil(priorActiveTokens) + newTokens : newTokens);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function assertEconomicPreflight(projectedTokens, budget, allowOverride) {
|
|
118
|
+
if (allowOverride) return;
|
|
119
|
+
if (budget.pricingThresholdTokens != null && projectedTokens > budget.pricingThresholdTokens) {
|
|
120
|
+
throw new Error(`context_preflight_pricing_threshold:${projectedTokens}>${budget.pricingThresholdTokens}`);
|
|
121
|
+
}
|
|
122
|
+
if (projectedTokens >= budget.maxNormalInputTokens) throw new Error(`context_preflight_max_normal:${projectedTokens}>=${budget.maxNormalInputTokens}`);
|
|
123
|
+
if (projectedTokens >= budget.compactAtTokens) throw new Error(`context_preflight_compaction_required:${projectedTokens}>=${budget.compactAtTokens}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function runResponsesAgent(options = {}) {
|
|
127
|
+
const provider = options.provider;
|
|
128
|
+
if (!provider?.create || !provider?.continueWithToolOutputs) throw new TypeError('Responses provider adapter is required');
|
|
129
|
+
if (!options.capabilityAdapter) throw new TypeError('capabilityAdapter is required');
|
|
130
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
131
|
+
const objective = clean(options.prompt);
|
|
132
|
+
if (!objective) throw new TypeError('prompt is required');
|
|
133
|
+
const record = getModelRecord(options.model);
|
|
134
|
+
if (!record) throw new RangeError(`unknown model: ${options.model}`);
|
|
135
|
+
const reasoningEffort = clean(options.reasoningEffort || 'low');
|
|
136
|
+
const serviceTier = clean(options.serviceTier || 'default');
|
|
137
|
+
const budget = modelBudget(record);
|
|
138
|
+
const instructionSet = options.instructions == null ? compileAgentInstructions(cwd, { maxChars: budget.maxSystemChars }) : null;
|
|
139
|
+
const instructions = options.instructions == null ? instructionSet.content : String(options.instructions);
|
|
140
|
+
const toolSurface = buildAgentToolSurface(options.capabilityAdapter, objective, options);
|
|
141
|
+
const emit = options.emit;
|
|
142
|
+
const runId = options.runId;
|
|
143
|
+
event(emit, 'tool.search', toolSurface.receipt, runId);
|
|
144
|
+
|
|
145
|
+
let previousResponseId = clean(options.previousResponseId) || null;
|
|
146
|
+
let priorActiveTokens = options.previousUsageSnapshot?.current_context?.input_tokens;
|
|
147
|
+
let input = objective;
|
|
148
|
+
let compacted = null;
|
|
149
|
+
let projected = projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget });
|
|
150
|
+
|
|
151
|
+
if (projected >= budget.compactAtTokens && previousResponseId && options.autoCompact !== false && typeof provider.compact === 'function') {
|
|
152
|
+
compacted = await provider.compact({
|
|
153
|
+
model: record.provider_model_id,
|
|
154
|
+
previousResponseId,
|
|
155
|
+
instructions,
|
|
156
|
+
promptCacheKey: options.promptCacheKey,
|
|
157
|
+
emit,
|
|
158
|
+
runId,
|
|
159
|
+
});
|
|
160
|
+
input = [...(compacted.output || []), userMessage(objective)];
|
|
161
|
+
previousResponseId = null;
|
|
162
|
+
priorActiveTokens = null;
|
|
163
|
+
projected = projectedInputTokens({ instructions, input, toolSurface, priorActiveTokens, budget });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
assertEconomicPreflight(projected, budget, options.allowEconomicOverride === true);
|
|
167
|
+
const pressure = assessContextUsage(projected, budget);
|
|
168
|
+
const maxOutputTokens = Number.isInteger(options.maxOutputTokens) && options.maxOutputTokens > 0
|
|
169
|
+
? Math.min(options.maxOutputTokens, record.max_output_tokens)
|
|
170
|
+
: Math.min(32_768, record.max_output_tokens);
|
|
171
|
+
const projectedCost = calculateModelCost(record, { input_tokens: projected, output_tokens: maxOutputTokens }, { serviceTier });
|
|
172
|
+
if (Number.isFinite(options.maxCallCostUsd) && projectedCost.total_usd > options.maxCallCostUsd) {
|
|
173
|
+
throw new Error(`projected_call_cost_exceeds_budget:${projectedCost.total_usd.toFixed(6)}>${Number(options.maxCallCostUsd).toFixed(6)}`);
|
|
174
|
+
}
|
|
175
|
+
const preflight = Object.freeze({
|
|
176
|
+
model: record.provider_model_id,
|
|
177
|
+
reasoning_effort: reasoningEffort,
|
|
178
|
+
service_tier: serviceTier,
|
|
179
|
+
estimated_input_tokens: projected,
|
|
180
|
+
max_output_tokens: maxOutputTokens,
|
|
181
|
+
projected_max_call_cost_usd: projectedCost.total_usd,
|
|
182
|
+
pricing_threshold_tokens: budget.pricingThresholdTokens,
|
|
183
|
+
tokens_until_pricing_threshold: pressure.tokensUntilPricingThreshold,
|
|
184
|
+
compacted_before_turn: Boolean(compacted),
|
|
185
|
+
estimate_kind: 'local',
|
|
186
|
+
});
|
|
187
|
+
if (typeof options.beforeRequest === 'function') {
|
|
188
|
+
const approved = await options.beforeRequest(preflight);
|
|
189
|
+
if (approved === false) throw new Error('model_request_not_approved');
|
|
190
|
+
}
|
|
191
|
+
event(emit, 'context.snapshot', {
|
|
192
|
+
estimate_kind: 'local',
|
|
193
|
+
estimated_input_tokens: projected,
|
|
194
|
+
window_tokens: budget.windowTokens,
|
|
195
|
+
utilization_ratio: pressure.utilizationRatio,
|
|
196
|
+
pricing_threshold_tokens: budget.pricingThresholdTokens,
|
|
197
|
+
tokens_until_pricing_threshold: pressure.tokensUntilPricingThreshold,
|
|
198
|
+
pressure: pressure.stage,
|
|
199
|
+
projected_max_call_cost_usd: projectedCost.total_usd,
|
|
200
|
+
tool_surface: toolSurface.receipt,
|
|
201
|
+
}, runId);
|
|
202
|
+
|
|
203
|
+
let cumulativeUsage = options.cumulativeUsage || null;
|
|
204
|
+
let totalCostUsd = 0;
|
|
205
|
+
let response = await provider.create({
|
|
206
|
+
model: record.provider_model_id,
|
|
207
|
+
input,
|
|
208
|
+
instructions,
|
|
209
|
+
reasoningEffort,
|
|
210
|
+
serviceTier,
|
|
211
|
+
tools: toolSurface.tools,
|
|
212
|
+
previousResponseId: previousResponseId || undefined,
|
|
213
|
+
maxOutputTokens,
|
|
214
|
+
promptCacheKey: options.promptCacheKey,
|
|
215
|
+
cumulativeUsage,
|
|
216
|
+
emit,
|
|
217
|
+
runId,
|
|
218
|
+
});
|
|
219
|
+
totalCostUsd += response.cost?.total_usd || 0;
|
|
220
|
+
cumulativeUsage = response.usage_snapshot?.cumulative || cumulativeUsage;
|
|
221
|
+
|
|
222
|
+
const toolReceipts = [];
|
|
223
|
+
const maxToolRounds = Number.isInteger(options.maxToolRounds) && options.maxToolRounds > 0 ? options.maxToolRounds : 8;
|
|
224
|
+
let rounds = 0;
|
|
225
|
+
while (response.tool_calls?.length) {
|
|
226
|
+
rounds += 1;
|
|
227
|
+
if (rounds > maxToolRounds) throw new Error(`tool_round_limit_exceeded:${maxToolRounds}`);
|
|
228
|
+
const outputs = [];
|
|
229
|
+
const activeTokens = response.usage_snapshot?.current_context?.input_tokens;
|
|
230
|
+
const maxToolChars = toolResultCharBudget(activeTokens, budget);
|
|
231
|
+
for (const call of response.tool_calls) {
|
|
232
|
+
const capabilityId = toolSurface.aliases.get(call.name);
|
|
233
|
+
if (!capabilityId) throw new Error(`unrecognized_tool_call:${call.name}`);
|
|
234
|
+
const descriptor = toolSurface.descriptors.find((row) => row.name === capabilityId);
|
|
235
|
+
const args = sanitizeToolInput(parseArguments(call.arguments), descriptor, cwd);
|
|
236
|
+
if (typeof options.beforeTool === 'function') {
|
|
237
|
+
const approved = await options.beforeTool({
|
|
238
|
+
call_id: call.call_id,
|
|
239
|
+
capability_id: capabilityId,
|
|
240
|
+
descriptor: descriptor ? { ...descriptor, input_schema: undefined } : null,
|
|
241
|
+
input: args,
|
|
242
|
+
cwd,
|
|
243
|
+
});
|
|
244
|
+
if (approved === false) {
|
|
245
|
+
const denied = new Error(`tool_execution_not_approved:${capabilityId}`);
|
|
246
|
+
denied.code = 'AGENTSAM_TOOL_NOT_APPROVED';
|
|
247
|
+
const diagnostic = diagnosticFromError(denied, { source: 'tool', kind: 'tool_execution_denied' });
|
|
248
|
+
event(emit, 'tool.failed', { call_id: call.call_id, capability_id: capabilityId, error: diagnostic }, runId);
|
|
249
|
+
event(emit, 'error.observed', { ...diagnostic, call_id: call.call_id, capability_id: capabilityId }, runId);
|
|
250
|
+
throw denied;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
event(emit, 'tool.started', { call_id: call.call_id, capability_id: capabilityId }, runId);
|
|
254
|
+
try {
|
|
255
|
+
const value = await options.capabilityAdapter.invoke(capabilityId, args);
|
|
256
|
+
const bounded = boundedToolOutput(value, call.call_id, maxToolChars);
|
|
257
|
+
toolReceipts.push(Object.freeze({ call_id: call.call_id, capability_id: capabilityId, ...bounded, output: undefined }));
|
|
258
|
+
outputs.push({ call_id: call.call_id, output: bounded.output });
|
|
259
|
+
event(emit, 'tool.completed', {
|
|
260
|
+
call_id: call.call_id,
|
|
261
|
+
capability_id: capabilityId,
|
|
262
|
+
result_ref: bounded.ref,
|
|
263
|
+
result_hash: bounded.hash,
|
|
264
|
+
result_chars: bounded.chars,
|
|
265
|
+
source_chars: bounded.source_chars,
|
|
266
|
+
truncated: bounded.truncated,
|
|
267
|
+
}, runId);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const diagnostic = diagnosticFromError(error, { source: 'tool', kind: 'tool_execution_error' });
|
|
270
|
+
event(emit, 'tool.failed', { call_id: call.call_id, capability_id: capabilityId, error: diagnostic }, runId);
|
|
271
|
+
event(emit, 'error.observed', { ...diagnostic, call_id: call.call_id, capability_id: capabilityId }, runId);
|
|
272
|
+
outputs.push({ call_id: call.call_id, output: JSON.stringify({ ok: false, error: diagnostic }) });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
response = await provider.continueWithToolOutputs({
|
|
276
|
+
model: record.provider_model_id,
|
|
277
|
+
previousResponseId: response.response_id,
|
|
278
|
+
toolOutputs: outputs,
|
|
279
|
+
instructions,
|
|
280
|
+
reasoningEffort,
|
|
281
|
+
serviceTier,
|
|
282
|
+
tools: toolSurface.tools,
|
|
283
|
+
maxOutputTokens,
|
|
284
|
+
promptCacheKey: options.promptCacheKey,
|
|
285
|
+
cumulativeUsage,
|
|
286
|
+
emit,
|
|
287
|
+
runId,
|
|
288
|
+
});
|
|
289
|
+
totalCostUsd += response.cost?.total_usd || 0;
|
|
290
|
+
cumulativeUsage = response.usage_snapshot?.cumulative || cumulativeUsage;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const active = response.usage_snapshot?.current_context?.input_tokens ?? 0;
|
|
294
|
+
const finalPressure = assessContextUsage(active, budget);
|
|
295
|
+
event(emit, 'context.snapshot', {
|
|
296
|
+
estimate_kind: 'provider',
|
|
297
|
+
active_input_tokens: active,
|
|
298
|
+
window_tokens: budget.windowTokens,
|
|
299
|
+
utilization_ratio: finalPressure.utilizationRatio,
|
|
300
|
+
pricing_threshold_tokens: budget.pricingThresholdTokens,
|
|
301
|
+
tokens_until_pricing_threshold: finalPressure.tokensUntilPricingThreshold,
|
|
302
|
+
pressure: finalPressure.stage,
|
|
303
|
+
compact_before_next_turn: finalPressure.shouldCompact,
|
|
304
|
+
}, runId);
|
|
305
|
+
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
output_text: response.output_text || '',
|
|
308
|
+
response_id: response.response_id,
|
|
309
|
+
model: record.provider_model_id,
|
|
310
|
+
reasoning_effort: reasoningEffort,
|
|
311
|
+
requested_service_tier: serviceTier,
|
|
312
|
+
actual_service_tier: response.actual_service_tier,
|
|
313
|
+
usage_snapshot: response.usage_snapshot,
|
|
314
|
+
cumulative_usage: cumulativeUsage,
|
|
315
|
+
total_cost_usd: totalCostUsd,
|
|
316
|
+
tool_surface: toolSurface.receipt,
|
|
317
|
+
tool_receipts: Object.freeze(toolReceipts),
|
|
318
|
+
compacted_before_turn: Boolean(compacted),
|
|
319
|
+
continuation: Object.freeze({
|
|
320
|
+
previous_response_id: response.response_id,
|
|
321
|
+
usage_snapshot: response.usage_snapshot,
|
|
322
|
+
compact_before_next_turn: finalPressure.shouldCompact,
|
|
323
|
+
}),
|
|
324
|
+
});
|
|
325
|
+
}
|