nexus-ai-pro 2.1.0 → 2.3.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/API_STABILITY.md +77 -0
- package/CHANGELOG.md +286 -1
- package/README.md +19 -9
- package/dist/agent/capabilities.d.ts +39 -0
- package/dist/agent/capabilities.js +36 -0
- package/dist/agent/create-agent.d.ts +61 -11
- package/dist/agent/create-agent.js +136 -22
- package/dist/agent/index.d.ts +3 -2
- package/dist/agent/index.js +1 -1
- package/dist/agent/middleware/approval.d.ts +26 -0
- package/dist/agent/middleware/approval.js +23 -0
- package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist/agent/middleware/context-editor.d.ts +52 -0
- package/dist/agent/middleware/context-editor.js +66 -0
- package/dist/agent/middleware/filesystem.d.ts +44 -0
- package/dist/agent/middleware/filesystem.js +83 -0
- package/dist/agent/middleware/index.d.ts +19 -0
- package/dist/agent/middleware/index.js +9 -0
- package/dist/agent/middleware/limits.d.ts +42 -0
- package/dist/agent/middleware/limits.js +52 -0
- package/dist/agent/middleware/models.d.ts +48 -0
- package/dist/agent/middleware/models.js +44 -0
- package/dist/agent/middleware/pii.d.ts +67 -0
- package/dist/agent/middleware/pii.js +173 -0
- package/dist/agent/middleware/retry.d.ts +66 -0
- package/dist/agent/middleware/retry.js +59 -0
- package/dist/agent/middleware/shared.d.ts +41 -0
- package/dist/agent/middleware/shared.js +73 -0
- package/dist/agent/middleware/tool-selector.d.ts +64 -0
- package/dist/agent/middleware/tool-selector.js +106 -0
- package/dist/agent/permissions.d.ts +113 -0
- package/dist/agent/permissions.js +180 -0
- package/dist/agent/sandbox.d.ts +151 -0
- package/dist/agent/sandbox.js +257 -0
- package/dist/agent/tool.d.ts +8 -3
- package/dist/agent/tool.js +14 -3
- package/dist/cache/memory-cache.d.ts +8 -0
- package/dist/cache/memory-cache.js +9 -0
- package/dist/cache/semantic-cache.d.ts +6 -3
- package/dist/cache/semantic-cache.js +5 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/db.d.ts +16 -4
- package/dist/cli/db.js +169 -22
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.js +53 -0
- package/dist/cli.js +12 -1
- package/dist/core/lifecycle.js +11 -6
- package/dist/core/nexus.d.ts +6 -0
- package/dist/core/nexus.js +103 -22
- package/dist/deep-agent/index.d.ts +109 -0
- package/dist/deep-agent/index.js +216 -0
- package/dist/doctor/index.d.ts +90 -0
- package/dist/doctor/index.js +237 -0
- package/dist/graph/functional.d.ts +89 -7
- package/dist/graph/functional.js +156 -36
- package/dist/graph/graph.d.ts +15 -5
- package/dist/graph/graph.js +95 -22
- package/dist/graph/lint.d.ts +4 -1
- package/dist/graph/lint.js +35 -0
- package/dist/images/evals.js +4 -2
- package/dist/operations/adapters.d.ts +52 -4
- package/dist/operations/adapters.js +194 -7
- package/dist/operations/runner.js +1 -0
- package/dist/operations/stats.js +8 -1
- package/dist/operations/store.d.ts +5 -1
- package/dist/operations/store.js +7 -0
- package/dist/ops/circuit-breaker.d.ts +52 -0
- package/dist/ops/circuit-breaker.js +4 -0
- package/dist/ops/circuit-store.d.ts +13 -2
- package/dist/ops/circuit-store.js +68 -0
- package/dist/ops/circuit-sync.d.ts +17 -0
- package/dist/ops/circuit-sync.js +86 -0
- package/dist/ops/rate-limiter.d.ts +2 -0
- package/dist/ops/rate-limiter.js +2 -0
- package/dist/postgres/circuits.d.ts +21 -4
- package/dist/postgres/circuits.js +65 -4
- package/dist/postgres/client.d.ts +0 -2
- package/dist/postgres/client.js +0 -4
- package/dist/postgres/evaluate.d.ts +16 -4
- package/dist/postgres/evaluate.js +17 -5
- package/dist/postgres/fulltext.d.ts +67 -0
- package/dist/postgres/fulltext.js +91 -0
- package/dist/postgres/index.d.ts +32 -11
- package/dist/postgres/index.js +65 -31
- package/dist/postgres/migrations.d.ts +60 -0
- package/dist/postgres/migrations.js +121 -0
- package/dist/postgres/operations.d.ts +14 -3
- package/dist/postgres/operations.js +45 -15
- package/dist/postgres/prompts.d.ts +11 -2
- package/dist/postgres/prompts.js +14 -3
- package/dist/postgres/rollups.d.ts +35 -0
- package/dist/postgres/rollups.js +129 -0
- package/dist/postgres/store.d.ts +11 -2
- package/dist/postgres/store.js +14 -3
- package/dist/postgres/traces.d.ts +13 -3
- package/dist/postgres/traces.js +25 -3
- package/dist/postgres/vectors.d.ts +11 -2
- package/dist/postgres/vectors.js +14 -3
- package/dist/rag/elasticsearch.d.ts +71 -0
- package/dist/rag/elasticsearch.js +130 -0
- package/dist/rag/pipeline.d.ts +140 -0
- package/dist/rag/pipeline.js +187 -0
- package/dist/rag/rerankers.d.ts +92 -0
- package/dist/rag/rerankers.js +97 -0
- package/dist/rag/retrievers.d.ts +12 -1
- package/dist/router/auto-router.d.ts +34 -7
- package/dist/router/auto-router.js +201 -68
- package/dist/server/assistant.d.ts +9 -1
- package/dist/server/assistant.js +10 -0
- package/dist/server/auth.d.ts +250 -0
- package/dist/server/auth.js +392 -0
- package/dist/server/errors.d.ts +12 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/runs.d.ts +24 -1
- package/dist/server/runs.js +90 -14
- package/dist/sqlite/index.d.ts +16 -3
- package/dist/sqlite/index.js +16 -3
- package/dist/sqlite/migrations.d.ts +26 -0
- package/dist/sqlite/migrations.js +63 -0
- package/dist/sqlite/operations.d.ts +14 -3
- package/dist/sqlite/operations.js +42 -15
- package/dist/sqlite/store.d.ts +9 -2
- package/dist/sqlite/store.js +13 -3
- package/dist/sqlite/vectors.d.ts +11 -2
- package/dist/sqlite/vectors.js +13 -2
- package/dist/store/memory.d.ts +10 -0
- package/dist/store/memory.js +10 -0
- package/dist/store/tenant.d.ts +12 -0
- package/dist/store/tenant.js +29 -0
- package/dist/tenancy/index.d.ts +113 -0
- package/dist/tenancy/index.js +233 -0
- package/dist/tracing/instrument.js +2 -0
- package/dist/types/config.d.ts +24 -2
- package/dist/types/graph.d.ts +49 -1
- package/dist/types/messages.d.ts +41 -2
- package/dist/types/operations.d.ts +6 -0
- package/dist/types/principal.d.ts +21 -0
- package/dist/types/principal.js +1 -0
- package/dist/types/response.d.ts +20 -0
- package/dist/types/server.d.ts +12 -10
- package/dist/utils/schema-migrations.d.ts +97 -0
- package/dist/utils/schema-migrations.js +91 -0
- package/dist/utils/signals.d.ts +17 -0
- package/dist/utils/signals.js +26 -0
- package/dist/utils/tenant.d.ts +25 -0
- package/dist/utils/tenant.js +30 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/agent/capabilities.d.ts +39 -0
- package/dist-cjs/agent/capabilities.js +41 -0
- package/dist-cjs/agent/create-agent.d.ts +61 -11
- package/dist-cjs/agent/create-agent.js +136 -22
- package/dist-cjs/agent/index.d.ts +3 -2
- package/dist-cjs/agent/index.js +4 -4
- package/dist-cjs/agent/middleware/approval.d.ts +26 -0
- package/dist-cjs/agent/middleware/approval.js +26 -0
- package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
- package/dist-cjs/agent/middleware/context-editor.js +70 -0
- package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
- package/dist-cjs/agent/middleware/filesystem.js +86 -0
- package/dist-cjs/agent/middleware/index.d.ts +19 -0
- package/dist-cjs/agent/middleware/index.js +28 -0
- package/dist-cjs/agent/middleware/limits.d.ts +42 -0
- package/dist-cjs/agent/middleware/limits.js +57 -0
- package/dist-cjs/agent/middleware/models.d.ts +48 -0
- package/dist-cjs/agent/middleware/models.js +48 -0
- package/dist-cjs/agent/middleware/pii.d.ts +67 -0
- package/dist-cjs/agent/middleware/pii.js +178 -0
- package/dist-cjs/agent/middleware/retry.d.ts +66 -0
- package/dist-cjs/agent/middleware/retry.js +63 -0
- package/dist-cjs/agent/middleware/shared.d.ts +41 -0
- package/dist-cjs/agent/middleware/shared.js +83 -0
- package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
- package/dist-cjs/agent/middleware/tool-selector.js +109 -0
- package/dist-cjs/agent/permissions.d.ts +113 -0
- package/dist-cjs/agent/permissions.js +189 -0
- package/dist-cjs/agent/sandbox.d.ts +151 -0
- package/dist-cjs/agent/sandbox.js +297 -0
- package/dist-cjs/agent/tool.d.ts +8 -3
- package/dist-cjs/agent/tool.js +14 -3
- package/dist-cjs/cache/memory-cache.d.ts +8 -0
- package/dist-cjs/cache/memory-cache.js +9 -0
- package/dist-cjs/cache/semantic-cache.d.ts +6 -3
- package/dist-cjs/cache/semantic-cache.js +5 -2
- package/dist-cjs/cli/args.js +3 -0
- package/dist-cjs/cli/db.d.ts +16 -4
- package/dist-cjs/cli/db.js +200 -20
- package/dist-cjs/cli/doctor.d.ts +17 -0
- package/dist-cjs/cli/doctor.js +56 -0
- package/dist-cjs/core/lifecycle.js +11 -6
- package/dist-cjs/core/nexus.d.ts +6 -0
- package/dist-cjs/core/nexus.js +103 -22
- package/dist-cjs/deep-agent/index.d.ts +109 -0
- package/dist-cjs/deep-agent/index.js +219 -0
- package/dist-cjs/doctor/index.d.ts +90 -0
- package/dist-cjs/doctor/index.js +276 -0
- package/dist-cjs/graph/functional.d.ts +89 -7
- package/dist-cjs/graph/functional.js +155 -35
- package/dist-cjs/graph/graph.d.ts +15 -5
- package/dist-cjs/graph/graph.js +95 -22
- package/dist-cjs/graph/lint.d.ts +4 -1
- package/dist-cjs/graph/lint.js +35 -0
- package/dist-cjs/images/evals.js +4 -2
- package/dist-cjs/operations/adapters.d.ts +52 -4
- package/dist-cjs/operations/adapters.js +193 -6
- package/dist-cjs/operations/runner.js +1 -0
- package/dist-cjs/operations/stats.js +8 -1
- package/dist-cjs/operations/store.d.ts +5 -1
- package/dist-cjs/operations/store.js +7 -0
- package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
- package/dist-cjs/ops/circuit-breaker.js +4 -0
- package/dist-cjs/ops/circuit-store.d.ts +13 -2
- package/dist-cjs/ops/circuit-store.js +68 -0
- package/dist-cjs/ops/circuit-sync.d.ts +17 -0
- package/dist-cjs/ops/circuit-sync.js +87 -0
- package/dist-cjs/ops/rate-limiter.d.ts +2 -0
- package/dist-cjs/ops/rate-limiter.js +2 -0
- package/dist-cjs/postgres/circuits.d.ts +21 -4
- package/dist-cjs/postgres/circuits.js +98 -3
- package/dist-cjs/postgres/client.d.ts +0 -2
- package/dist-cjs/postgres/client.js +0 -5
- package/dist-cjs/postgres/evaluate.d.ts +16 -4
- package/dist-cjs/postgres/evaluate.js +50 -4
- package/dist-cjs/postgres/fulltext.d.ts +67 -0
- package/dist-cjs/postgres/fulltext.js +129 -0
- package/dist-cjs/postgres/index.d.ts +32 -11
- package/dist-cjs/postgres/index.js +69 -20
- package/dist-cjs/postgres/migrations.d.ts +60 -0
- package/dist-cjs/postgres/migrations.js +129 -0
- package/dist-cjs/postgres/operations.d.ts +14 -3
- package/dist-cjs/postgres/operations.js +78 -14
- package/dist-cjs/postgres/prompts.d.ts +11 -2
- package/dist-cjs/postgres/prompts.js +47 -2
- package/dist-cjs/postgres/rollups.d.ts +35 -0
- package/dist-cjs/postgres/rollups.js +168 -0
- package/dist-cjs/postgres/store.d.ts +11 -2
- package/dist-cjs/postgres/store.js +47 -2
- package/dist-cjs/postgres/traces.d.ts +13 -3
- package/dist-cjs/postgres/traces.js +58 -2
- package/dist-cjs/postgres/vectors.d.ts +11 -2
- package/dist-cjs/postgres/vectors.js +47 -2
- package/dist-cjs/rag/elasticsearch.d.ts +71 -0
- package/dist-cjs/rag/elasticsearch.js +135 -0
- package/dist-cjs/rag/pipeline.d.ts +140 -0
- package/dist-cjs/rag/pipeline.js +191 -0
- package/dist-cjs/rag/rerankers.d.ts +92 -0
- package/dist-cjs/rag/rerankers.js +107 -0
- package/dist-cjs/rag/retrievers.d.ts +12 -1
- package/dist-cjs/router/auto-router.d.ts +34 -7
- package/dist-cjs/router/auto-router.js +201 -68
- package/dist-cjs/server/assistant.d.ts +9 -1
- package/dist-cjs/server/assistant.js +10 -0
- package/dist-cjs/server/auth.d.ts +250 -0
- package/dist-cjs/server/auth.js +402 -0
- package/dist-cjs/server/errors.d.ts +12 -0
- package/dist-cjs/server/errors.js +9 -1
- package/dist-cjs/server/index.d.ts +1 -1
- package/dist-cjs/server/index.js +2 -1
- package/dist-cjs/server/runs.d.ts +24 -1
- package/dist-cjs/server/runs.js +89 -13
- package/dist-cjs/sqlite/index.d.ts +16 -3
- package/dist-cjs/sqlite/index.js +31 -10
- package/dist-cjs/sqlite/migrations.d.ts +26 -0
- package/dist-cjs/sqlite/migrations.js +71 -0
- package/dist-cjs/sqlite/operations.d.ts +14 -3
- package/dist-cjs/sqlite/operations.js +76 -15
- package/dist-cjs/sqlite/store.d.ts +9 -2
- package/dist-cjs/sqlite/store.js +47 -3
- package/dist-cjs/sqlite/vectors.d.ts +11 -2
- package/dist-cjs/sqlite/vectors.js +47 -2
- package/dist-cjs/store/memory.d.ts +10 -0
- package/dist-cjs/store/memory.js +10 -0
- package/dist-cjs/store/tenant.d.ts +12 -0
- package/dist-cjs/store/tenant.js +33 -0
- package/dist-cjs/tenancy/index.d.ts +113 -0
- package/dist-cjs/tenancy/index.js +250 -0
- package/dist-cjs/tracing/instrument.js +2 -0
- package/dist-cjs/types/config.d.ts +24 -2
- package/dist-cjs/types/graph.d.ts +49 -1
- package/dist-cjs/types/messages.d.ts +41 -2
- package/dist-cjs/types/operations.d.ts +6 -0
- package/dist-cjs/types/principal.d.ts +21 -0
- package/dist-cjs/types/principal.js +2 -0
- package/dist-cjs/types/response.d.ts +20 -0
- package/dist-cjs/types/server.d.ts +12 -10
- package/dist-cjs/utils/schema-migrations.d.ts +97 -0
- package/dist-cjs/utils/schema-migrations.js +102 -0
- package/dist-cjs/utils/signals.d.ts +17 -0
- package/dist-cjs/utils/signals.js +29 -0
- package/dist-cjs/utils/tenant.d.ts +25 -0
- package/dist-cjs/utils/tenant.js +37 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +6 -4
- package/package.json +188 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { OperationLifecycleLike } from '../types/lifecycle.js';
|
|
2
2
|
import { type CompiledGraph } from '../graph/graph.js';
|
|
3
|
-
import type { GraphCheckpointer, NodeContext } from '../types/graph.js';
|
|
3
|
+
import type { GraphCheckpointer, InterruptRequest, NodeContext } from '../types/graph.js';
|
|
4
4
|
import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
|
|
5
5
|
import type { NexusResponse, StreamChunk } from '../types/response.js';
|
|
6
|
+
import type { Principal } from '../types/principal.js';
|
|
6
7
|
import type { Store } from '../types/store.js';
|
|
8
|
+
import type { PermissionPolicyLike } from './permissions.js';
|
|
7
9
|
/**
|
|
8
10
|
* An agent as a graph.
|
|
9
11
|
*
|
|
@@ -28,6 +30,10 @@ export interface AgentToolCall {
|
|
|
28
30
|
name: string;
|
|
29
31
|
/** Arguments, parsed from the model's JSON. */
|
|
30
32
|
args: Record<string, unknown>;
|
|
33
|
+
/** What the call declares it does, when the tool declares capabilities. */
|
|
34
|
+
capabilities?: readonly string[];
|
|
35
|
+
/** Why the permission policy asked for approval, when it did. */
|
|
36
|
+
permission?: string;
|
|
31
37
|
}
|
|
32
38
|
/** What an approver may answer: allow, refuse with a reason, or allow with corrected arguments. */
|
|
33
39
|
export type AgentApproval = boolean | {
|
|
@@ -42,24 +48,58 @@ export interface AgentApprovalPolicy {
|
|
|
42
48
|
/** The question an operator sees. Defaults to naming the tool and its arguments. */
|
|
43
49
|
reason?: (call: AgentToolCall) => string;
|
|
44
50
|
}
|
|
51
|
+
/** What every middleware hook can read about the step it runs in. */
|
|
52
|
+
export interface AgentMiddlewareContext {
|
|
53
|
+
/** The agent's state at this step: the whole transcript, tool calls and results included. */
|
|
54
|
+
state: AgentState;
|
|
55
|
+
/** Long-term memory, when the agent has a store. */
|
|
56
|
+
store?: Store;
|
|
57
|
+
/** The thread the run belongs to. */
|
|
58
|
+
threadId: string;
|
|
59
|
+
/** The tenant the run is for, when it has one. */
|
|
60
|
+
tenantId?: string;
|
|
61
|
+
/** Who the run is for, when it has a principal: the caller the server authenticated. */
|
|
62
|
+
principal?: Readonly<Principal>;
|
|
63
|
+
/** Aborted when the run is cancelled or the step times out. A hook that waits should stop with it. */
|
|
64
|
+
signal: AbortSignal;
|
|
65
|
+
/**
|
|
66
|
+
* Suspends the run until a person answers, as `interruptOn` does. When the run resumes, the step
|
|
67
|
+
* runs again and this returns the answer instead of suspending.
|
|
68
|
+
*/
|
|
69
|
+
interrupt<T = unknown>(request: InterruptRequest): T;
|
|
70
|
+
}
|
|
71
|
+
/** What `wrapModelCall` can read, and how it ends the run without calling the model. */
|
|
72
|
+
export interface AgentModelCallContext extends AgentMiddlewareContext {
|
|
73
|
+
/**
|
|
74
|
+
* Ends the run with this answer and `stopReason: 'stopped'`, without another model call. Return
|
|
75
|
+
* what it returns: `return context.stop('The model call limit was reached.')`.
|
|
76
|
+
*/
|
|
77
|
+
stop(answer: string): NexusResponse;
|
|
78
|
+
}
|
|
45
79
|
/** Hooks around the agent's model calls and tool calls. Each hook is optional. */
|
|
46
80
|
export interface AgentMiddleware {
|
|
47
81
|
/** Names the middleware in traces and errors. */
|
|
48
82
|
name?: string;
|
|
49
83
|
/** Adjusts the request before it is sent: trim history, add context, swap the model. */
|
|
50
|
-
beforeModel?(context: {
|
|
84
|
+
beforeModel?(context: AgentMiddlewareContext & {
|
|
51
85
|
request: CompletionRequest;
|
|
52
|
-
state: AgentState;
|
|
53
|
-
store?: Store;
|
|
54
86
|
}): CompletionRequest | void | Promise<CompletionRequest | void>;
|
|
55
87
|
/** Inspects or replaces the response: redact, validate, count. */
|
|
56
|
-
afterModel?(context: {
|
|
88
|
+
afterModel?(context: AgentMiddlewareContext & {
|
|
57
89
|
response: NexusResponse;
|
|
58
|
-
state: AgentState;
|
|
59
|
-
store?: Store;
|
|
60
90
|
}): NexusResponse | void | Promise<NexusResponse | void>;
|
|
61
|
-
/**
|
|
62
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Wraps the model call itself, after every `beforeModel` and before every `afterModel`. `next`
|
|
93
|
+
* sends a request and may be called again, with the same request or a changed one, to retry or to
|
|
94
|
+
* fall back to another model. The first middleware is the outermost.
|
|
95
|
+
*/
|
|
96
|
+
wrapModelCall?(request: CompletionRequest, next: (request: CompletionRequest) => Promise<NexusResponse>, context: AgentModelCallContext): Promise<NexusResponse> | NexusResponse;
|
|
97
|
+
/**
|
|
98
|
+
* Wraps a tool call, so a policy can log it, time it, retry it, or refuse it. `next` runs the
|
|
99
|
+
* rest of the chain and may be called again; given a call, it runs that call's arguments instead,
|
|
100
|
+
* and the permission policy decides them again first. The first middleware is the outermost.
|
|
101
|
+
*/
|
|
102
|
+
wrapToolCall?(call: AgentToolCall, next: (call?: AgentToolCall) => Promise<AgentToolResult>, context: AgentMiddlewareContext): Promise<AgentToolResult> | AgentToolResult;
|
|
63
103
|
}
|
|
64
104
|
/** What a tool call produced. */
|
|
65
105
|
export interface AgentToolResult {
|
|
@@ -88,6 +128,13 @@ export interface CreateAgentOptions {
|
|
|
88
128
|
interruptOn?: Record<string, AgentApprovalPolicy | true>;
|
|
89
129
|
/** Hooks around model and tool calls, applied in order. */
|
|
90
130
|
middleware?: AgentMiddleware[];
|
|
131
|
+
/**
|
|
132
|
+
* Decides every tool call from the capabilities it declares, before it runs: allow it, deny it,
|
|
133
|
+
* or interrupt for a person's approval, as `interruptOn` does. A denied call reaches the model as a
|
|
134
|
+
* failed tool result naming the reason, and never runs. See `permissionPolicy()` in
|
|
135
|
+
* `nexus-ai-pro/agent/permissions`.
|
|
136
|
+
*/
|
|
137
|
+
permissions?: PermissionPolicyLike;
|
|
91
138
|
/** Long-term memory, available to tools and middleware as `store`. */
|
|
92
139
|
store?: Store;
|
|
93
140
|
/**
|
|
@@ -136,8 +183,11 @@ export type AgentState = {
|
|
|
136
183
|
/** Why the agent stopped. */
|
|
137
184
|
stopReason?: AgentStopReason;
|
|
138
185
|
};
|
|
139
|
-
/**
|
|
140
|
-
|
|
186
|
+
/**
|
|
187
|
+
* `completed` when the model answered, `max_iterations` when it ran out of model calls, `stopped`
|
|
188
|
+
* when a middleware ended the run through `context.stop()`.
|
|
189
|
+
*/
|
|
190
|
+
export type AgentStopReason = 'completed' | 'max_iterations' | 'stopped';
|
|
141
191
|
/** An agent: a compiled graph over the agent's channels. */
|
|
142
192
|
export type AgentGraph = CompiledGraph<AgentChannels>;
|
|
143
193
|
/** Wraps a question into the state an agent starts from. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendList, counter, lastValue } from '../graph/channels.js';
|
|
2
2
|
import { createGraph } from '../graph/graph.js';
|
|
3
3
|
import { Command, END, Send } from '../types/graph.js';
|
|
4
|
+
import { capabilitiesOf } from './capabilities.js';
|
|
4
5
|
import { ToolExecutor, toolMessageContent } from './tool.js';
|
|
5
6
|
const agentChannels = () => ({
|
|
6
7
|
messages: appendList(),
|
|
@@ -34,24 +35,47 @@ export function createAgent(options) {
|
|
|
34
35
|
...(options.responseFormat ? { responseFormat: options.responseFormat } : {}),
|
|
35
36
|
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
36
37
|
};
|
|
38
|
+
const hooks = hookContext(context, state, options.store);
|
|
37
39
|
for (const item of middleware) {
|
|
38
|
-
request = (await item.beforeModel?.({
|
|
40
|
+
request = (await item.beforeModel?.({ ...hooks, request })) ?? request;
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const streaming = Boolean(options.streamTokens && options.client.stream);
|
|
43
|
+
const send = (next) => streaming && options.client.stream
|
|
44
|
+
? streamed(options.client.stream(next), (chunk) => context.message(chunk))
|
|
45
|
+
: options.client.complete(next);
|
|
46
|
+
const stops = new WeakSet();
|
|
47
|
+
const modelContext = {
|
|
48
|
+
...hooks,
|
|
49
|
+
stop: (answer) => {
|
|
50
|
+
const stopped = stopResponse(answer, request.model);
|
|
51
|
+
stops.add(stopped);
|
|
52
|
+
return stopped;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const wrapped = middleware.reduceRight((next, item) => {
|
|
56
|
+
const wrap = item.wrapModelCall?.bind(item);
|
|
57
|
+
return wrap ? (current) => Promise.resolve(wrap(current, next, modelContext)) : next;
|
|
58
|
+
}, send);
|
|
59
|
+
let response = await wrapped(request);
|
|
60
|
+
const stopped = stops.has(response);
|
|
61
|
+
if ((!streaming || stopped) && response.content) {
|
|
44
62
|
context.message({ content: response.content });
|
|
45
63
|
}
|
|
46
64
|
for (const item of middleware) {
|
|
47
|
-
response = (await item.afterModel?.({
|
|
65
|
+
response = (await item.afterModel?.({ ...hooks, response })) ?? response;
|
|
48
66
|
}
|
|
49
67
|
const assistant = {
|
|
50
68
|
role: 'assistant',
|
|
51
69
|
content: response.content,
|
|
52
|
-
...(response.toolCalls?.length ? { toolCalls: response.toolCalls } : {}),
|
|
70
|
+
...(response.toolCalls?.length && !stopped ? { toolCalls: response.toolCalls } : {}),
|
|
53
71
|
};
|
|
54
|
-
const calls = response.toolCalls ?? [];
|
|
72
|
+
const calls = stopped ? [] : (response.toolCalls ?? []);
|
|
73
|
+
if (stopped) {
|
|
74
|
+
return new Command({
|
|
75
|
+
update: { messages: [assistant], iterations: 1, answer: response.content, stopReason: 'stopped' },
|
|
76
|
+
goto: END,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
55
79
|
if (calls.length === 0) {
|
|
56
80
|
return new Command({
|
|
57
81
|
update: { messages: [assistant], iterations: 1, answer: response.content, stopReason: 'completed' },
|
|
@@ -76,27 +100,82 @@ export function createAgent(options) {
|
|
|
76
100
|
return { messages: [toolMessage(call, { ok: false, error: parsed.error })] };
|
|
77
101
|
}
|
|
78
102
|
let args = parsed.args;
|
|
79
|
-
const
|
|
103
|
+
const definition = executor.get(call.function.name);
|
|
104
|
+
const capabilities = capabilitiesOf(definition, args);
|
|
105
|
+
const description = {
|
|
106
|
+
id: call.id,
|
|
107
|
+
name: call.function.name,
|
|
108
|
+
args,
|
|
109
|
+
...(capabilities ? { capabilities } : {}),
|
|
110
|
+
};
|
|
111
|
+
const refuse = (error) => {
|
|
112
|
+
context.tool({ phase: 'error', id: call.id, name: description.name, error });
|
|
113
|
+
return { messages: [toolMessage(call, { ok: false, error })] };
|
|
114
|
+
};
|
|
115
|
+
let permission;
|
|
116
|
+
if (options.permissions) {
|
|
117
|
+
const verdict = await options.permissions.decide({
|
|
118
|
+
tool: description.name,
|
|
119
|
+
args,
|
|
120
|
+
capabilities,
|
|
121
|
+
...(context.principal ? { principal: context.principal } : {}),
|
|
122
|
+
});
|
|
123
|
+
if (verdict.decision === 'deny')
|
|
124
|
+
return refuse(`Permission denied: ${verdict.reason}`);
|
|
125
|
+
if (verdict.decision === 'ask')
|
|
126
|
+
permission = verdict.reason;
|
|
127
|
+
}
|
|
80
128
|
const policy = approvals[description.name];
|
|
81
|
-
if (policy) {
|
|
129
|
+
if (policy || permission) {
|
|
130
|
+
const asked = permission ? { ...description, permission } : description;
|
|
82
131
|
const decision = context.interrupt({
|
|
83
|
-
reason: (policy
|
|
84
|
-
|
|
132
|
+
reason: (policy && policy !== true ? policy.reason?.(asked) : undefined) ??
|
|
133
|
+
(permission ? `${defaultReason(asked)} (${permission})` : defaultReason(asked)),
|
|
134
|
+
payload: asked,
|
|
85
135
|
});
|
|
86
136
|
const verdict = normalizeApproval(decision);
|
|
87
|
-
if (!verdict.approved)
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (verdict.args)
|
|
137
|
+
if (!verdict.approved)
|
|
138
|
+
return refuse(verdict.reason ?? 'A human refused this tool call');
|
|
139
|
+
if (verdict.args) {
|
|
93
140
|
args = verdict.args;
|
|
141
|
+
if (options.permissions) {
|
|
142
|
+
const recheck = await options.permissions.decide({
|
|
143
|
+
tool: description.name,
|
|
144
|
+
args,
|
|
145
|
+
capabilities: capabilitiesOf(definition, args),
|
|
146
|
+
});
|
|
147
|
+
if (recheck.decision === 'deny')
|
|
148
|
+
return refuse(`Permission denied: ${recheck.reason}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
94
151
|
}
|
|
95
|
-
const run = async () =>
|
|
152
|
+
const run = async (current) => {
|
|
153
|
+
if (options.permissions && current.args !== args) {
|
|
154
|
+
const recheck = await options.permissions.decide({
|
|
155
|
+
tool: description.name,
|
|
156
|
+
args: current.args,
|
|
157
|
+
capabilities: capabilitiesOf(definition, current.args),
|
|
158
|
+
});
|
|
159
|
+
if (recheck.decision === 'deny')
|
|
160
|
+
return { ok: false, error: `Permission denied: ${recheck.reason}` };
|
|
161
|
+
}
|
|
162
|
+
return executor.execute(description.name, current.args, {
|
|
163
|
+
toolCallId: call.id,
|
|
164
|
+
threadId: context.threadId,
|
|
165
|
+
...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
|
|
166
|
+
...(context.principal ? { principal: context.principal } : {}),
|
|
167
|
+
signal: context.signal,
|
|
168
|
+
...(context.store ? { store: context.store } : {}),
|
|
169
|
+
});
|
|
170
|
+
};
|
|
96
171
|
context.tool({ phase: 'start', id: call.id, name: description.name, args });
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
172
|
+
const hooks = hookContext(context, context.state, options.store);
|
|
173
|
+
const result = await middleware.reduceRight((next, item) => {
|
|
174
|
+
const wrap = item.wrapToolCall?.bind(item);
|
|
175
|
+
return wrap
|
|
176
|
+
? (current) => Promise.resolve(wrap(current, (changed) => next(changed ?? current), hooks))
|
|
177
|
+
: next;
|
|
178
|
+
}, run)({ ...description, args });
|
|
100
179
|
context.tool(result.ok
|
|
101
180
|
? { phase: 'result', id: call.id, name: description.name, result: result.result }
|
|
102
181
|
: { phase: 'error', id: call.id, name: description.name, error: result.error ?? 'The tool failed' });
|
|
@@ -111,6 +190,12 @@ export function createAgent(options) {
|
|
|
111
190
|
...(options.store ? { store: options.store } : {}),
|
|
112
191
|
...(options.name ? { name: options.name } : {}),
|
|
113
192
|
...(options.lifecycle ? { lifecycle: options.lifecycle, lifecycleFamily: 'agent' } : {}),
|
|
193
|
+
tools: executor.list().map((definition) => ({
|
|
194
|
+
name: definition.name,
|
|
195
|
+
...(Array.isArray(definition.capabilities) ? { capabilities: [...definition.capabilities] } : {}),
|
|
196
|
+
...(typeof definition.capabilities === 'function' ? { dynamic: true } : {}),
|
|
197
|
+
approval: approvals[definition.name] ? 'interrupt' : options.permissions ? 'policy' : 'none',
|
|
198
|
+
})),
|
|
114
199
|
});
|
|
115
200
|
}
|
|
116
201
|
export function agentAsTool(options) {
|
|
@@ -165,6 +250,35 @@ function parseArguments(raw) {
|
|
|
165
250
|
return { ok: false, error: `Tool arguments are not valid JSON (${reason}); the tool was not run` };
|
|
166
251
|
}
|
|
167
252
|
}
|
|
253
|
+
function hookContext(context, state, store) {
|
|
254
|
+
return {
|
|
255
|
+
state,
|
|
256
|
+
...((context.store ?? store) ? { store: context.store ?? store } : {}),
|
|
257
|
+
threadId: context.threadId,
|
|
258
|
+
...(context.tenantId ? { tenantId: context.tenantId } : {}),
|
|
259
|
+
...(context.principal ? { principal: context.principal } : {}),
|
|
260
|
+
signal: context.signal,
|
|
261
|
+
interrupt: (request) => context.interrupt(request),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function stopResponse(answer, model) {
|
|
265
|
+
return {
|
|
266
|
+
content: answer,
|
|
267
|
+
role: 'assistant',
|
|
268
|
+
finishReason: 'stop',
|
|
269
|
+
meta: {
|
|
270
|
+
requestId: '',
|
|
271
|
+
providerUsed: 'none',
|
|
272
|
+
modelUsed: model,
|
|
273
|
+
latencyMs: 0,
|
|
274
|
+
tokensInput: 0,
|
|
275
|
+
tokensOutput: 0,
|
|
276
|
+
tokensSaved: 0,
|
|
277
|
+
cacheHit: false,
|
|
278
|
+
guardrailsApplied: ['stopped-by-middleware'],
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
168
282
|
async function streamed(chunks, onChunk) {
|
|
169
283
|
let content = '';
|
|
170
284
|
const toolCalls = [];
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AgentLoop, type AgentModelClient as AgentLoopModelClient } from './loop.js';
|
|
2
2
|
export { isToolOutput, tool, ToolExecutor, toolMessageContent, toolOutput } from './tool.js';
|
|
3
|
-
export { limitToolCalls, redactMessages, type RedactOptions, summarizeHistory, type SummarizeOptions, } from './middleware.js';
|
|
4
|
-
export { agentAsTool, agentInput, type AgentApproval, type AgentApprovalPolicy, type AgentChannels, type AgentGraph, type AgentMiddleware, type AgentModelClient, type AgentState, type AgentStopReason, type AgentToolCall, type AgentToolResult, createAgent, type CreateAgentOptions, } from './create-agent.js';
|
|
3
|
+
export { limitToolCalls, redactMessages, type RedactOptions, summarizeHistory, type SummarizeOptions, } from './middleware/basic.js';
|
|
4
|
+
export { agentAsTool, agentInput, type AgentApproval, type AgentApprovalPolicy, type AgentChannels, type AgentGraph, type AgentMiddleware, type AgentMiddlewareContext, type AgentModelCallContext, type AgentModelClient, type AgentState, type AgentStopReason, type AgentToolCall, type AgentToolResult, createAgent, type CreateAgentOptions, } from './create-agent.js';
|
|
5
5
|
export type { AgentConfig, AgentResult, AgentStep, ToolExecutionResult } from '../types/agent.js';
|
|
6
|
+
export type { ToolContext } from '../types/messages.js';
|
package/dist/agent/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { AgentLoop } from './loop.js';
|
|
2
2
|
export { isToolOutput, tool, ToolExecutor, toolMessageContent, toolOutput } from './tool.js';
|
|
3
|
-
export { limitToolCalls, redactMessages, summarizeHistory, } from './middleware.js';
|
|
3
|
+
export { limitToolCalls, redactMessages, summarizeHistory, } from './middleware/basic.js';
|
|
4
4
|
export { agentAsTool, agentInput, createAgent, } from './create-agent.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentApproval, AgentMiddleware, AgentMiddlewareContext, AgentToolCall } from '../create-agent.js';
|
|
2
|
+
/** Options for `humanApproval()`. */
|
|
3
|
+
export interface HumanApprovalOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Which calls need a person's approval. Defaults to every call whose tool declares nothing, or
|
|
6
|
+
* declares a capability that does anything but read.
|
|
7
|
+
*/
|
|
8
|
+
when?: (call: AgentToolCall, context: AgentMiddlewareContext) => boolean;
|
|
9
|
+
/** The question a person sees. Defaults to naming the tool and its arguments. */
|
|
10
|
+
reason?: (call: AgentToolCall) => string;
|
|
11
|
+
/**
|
|
12
|
+
* Asks out of band instead of suspending the run: posts to a chat and waits for the answer, or
|
|
13
|
+
* applies a rule of your own. Without it, the run suspends through `interrupt()` until `resume()`
|
|
14
|
+
* answers, as `interruptOn` does.
|
|
15
|
+
*/
|
|
16
|
+
approve?: (call: AgentToolCall, context: AgentMiddlewareContext) => AgentApproval | Promise<AgentApproval>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Asks a person before a tool call runs, decided per call rather than per tool name.
|
|
20
|
+
*
|
|
21
|
+
* `interruptOn` approves every call to a named tool; this approves the calls a rule picks: a refund
|
|
22
|
+
* over a limit, a write outside a draft folder, anything a tool does that is not a read. An
|
|
23
|
+
* approver may refuse, or correct the arguments, which the permission policy then decides again.
|
|
24
|
+
* Put it before `toolRetry()`, so a retried call is not asked about twice.
|
|
25
|
+
*/
|
|
26
|
+
export declare function humanApproval(options?: HumanApprovalOptions): AgentMiddleware;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isSensitiveCapability } from '../capabilities.js';
|
|
2
|
+
export function humanApproval(options = {}) {
|
|
3
|
+
const when = options.when ??
|
|
4
|
+
((call) => call.capabilities === undefined || call.capabilities.some(isSensitiveCapability));
|
|
5
|
+
const reason = options.reason ?? ((call) => `Run tool "${call.name}" with ${JSON.stringify(call.args)}?`);
|
|
6
|
+
return {
|
|
7
|
+
name: 'human-approval',
|
|
8
|
+
async wrapToolCall(call, next, context) {
|
|
9
|
+
if (!when(call, context))
|
|
10
|
+
return next();
|
|
11
|
+
const answer = options.approve
|
|
12
|
+
? await options.approve(call, context)
|
|
13
|
+
: context.interrupt({ reason: reason(call), payload: call });
|
|
14
|
+
if (answer === false || (typeof answer === 'object' && !answer.approved)) {
|
|
15
|
+
const why = typeof answer === 'object' && !answer.approved ? answer.reason : undefined;
|
|
16
|
+
return { ok: false, error: why ?? 'A person refused this tool call' };
|
|
17
|
+
}
|
|
18
|
+
return typeof answer === 'object' && answer.approved && answer.args
|
|
19
|
+
? next({ ...call, args: answer.args })
|
|
20
|
+
: next();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Message } from '
|
|
2
|
-
import type { AgentMiddleware } from '
|
|
1
|
+
import type { Message } from '../../types/messages.js';
|
|
2
|
+
import type { AgentMiddleware } from '../create-agent.js';
|
|
3
3
|
/**
|
|
4
4
|
* Middleware that ships with the agent.
|
|
5
5
|
*
|
|
@@ -40,9 +40,10 @@ export interface RedactOptions {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function redactMessages(options: RedactOptions): AgentMiddleware;
|
|
42
42
|
/**
|
|
43
|
-
* Caps how often a tool may be called in one run.
|
|
43
|
+
* Caps how often a tool may be called in one run: one turn, from a user message to the answer.
|
|
44
44
|
*
|
|
45
45
|
* A model that loops on the same call otherwise burns the iteration budget, and with an expensive
|
|
46
|
-
* tool it burns money too.
|
|
46
|
+
* tool it burns money too. Calls are counted from the transcript, so the count survives a restart
|
|
47
|
+
* and a resumed approval, and two threads never share one.
|
|
47
48
|
*/
|
|
48
49
|
export declare function limitToolCalls(limits: Record<string, number>): AgentMiddleware;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callPosition } from './shared.js';
|
|
1
2
|
export function summarizeHistory(options) {
|
|
2
3
|
const keepLast = options.keepLast ?? 6;
|
|
3
4
|
const triggerAfter = options.triggerAfter ?? 20;
|
|
@@ -40,18 +41,15 @@ export function redactMessages(options) {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
export function limitToolCalls(limits) {
|
|
43
|
-
const counts = new Map();
|
|
44
44
|
return {
|
|
45
45
|
name: 'limit-tool-calls',
|
|
46
|
-
async wrapToolCall(call, next) {
|
|
46
|
+
async wrapToolCall(call, next, context) {
|
|
47
47
|
const limit = limits[call.name];
|
|
48
48
|
if (limit === undefined)
|
|
49
49
|
return next();
|
|
50
|
-
|
|
51
|
-
if (used >= limit) {
|
|
50
|
+
if (callPosition(context.state.messages, call) > limit) {
|
|
52
51
|
return { ok: false, error: `Tool "${call.name}" has already run ${limit} times in this run` };
|
|
53
52
|
}
|
|
54
|
-
counts.set(call.name, used + 1);
|
|
55
53
|
return next();
|
|
56
54
|
},
|
|
57
55
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CompletionRequest } from '../../types/messages.js';
|
|
2
|
+
import type { AgentMiddleware } from '../create-agent.js';
|
|
3
|
+
/** One cleared result, for a custom placeholder. */
|
|
4
|
+
export interface ClearedToolResult {
|
|
5
|
+
/** The tool whose result was cleared. */
|
|
6
|
+
tool: string;
|
|
7
|
+
/** How long the result was, in characters. */
|
|
8
|
+
length: number;
|
|
9
|
+
}
|
|
10
|
+
/** What one edit did, for logs and for measuring what it saves. */
|
|
11
|
+
export interface ContextEdit {
|
|
12
|
+
/** Tool results cleared. */
|
|
13
|
+
cleared: number;
|
|
14
|
+
/** The request's estimated tokens before. */
|
|
15
|
+
tokensBefore: number;
|
|
16
|
+
/** And after. */
|
|
17
|
+
tokensAfter: number;
|
|
18
|
+
}
|
|
19
|
+
/** Options for `contextEditor()`. */
|
|
20
|
+
export interface ContextEditorOptions {
|
|
21
|
+
/** Editing starts once the request's estimated tokens pass this. Defaults to 30,000. */
|
|
22
|
+
triggerTokens?: number;
|
|
23
|
+
/** The most recent tool results, kept whole. Defaults to 3. */
|
|
24
|
+
keepToolResults?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Results are cleared this many at a time, so the edited prefix stays the same for several calls
|
|
27
|
+
* and a provider's prompt cache keeps working between edits. Defaults to 5.
|
|
28
|
+
*/
|
|
29
|
+
batch?: number;
|
|
30
|
+
/** Tools whose results are never cleared. */
|
|
31
|
+
exclude?: readonly string[];
|
|
32
|
+
/** Also empties the arguments of each call whose result is cleared, for tools that take large inputs. */
|
|
33
|
+
clearArguments?: boolean;
|
|
34
|
+
/** What a cleared result says instead. Defaults to a line naming the tool and how to get it back. */
|
|
35
|
+
placeholder?: string | ((cleared: ClearedToolResult) => string);
|
|
36
|
+
/** Estimates a request's tokens. Defaults to about four characters a token. */
|
|
37
|
+
estimateTokens?: (request: CompletionRequest) => number;
|
|
38
|
+
/** Called after each edit. */
|
|
39
|
+
onEdit?: (edit: ContextEdit) => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clears old tool results from what the model is sent, once the request grows past a threshold.
|
|
43
|
+
*
|
|
44
|
+
* Tool output is most of a long agent's context, and the model rarely needs a result from twenty
|
|
45
|
+
* steps ago verbatim. Every result but the most recent few is replaced by a short placeholder, in
|
|
46
|
+
* batches, so the edited prefix stays the same between edits and a provider's prompt cache keeps
|
|
47
|
+
* working. Only the request changes: the transcript in state keeps every result, and checkpoints,
|
|
48
|
+
* events, and a later summary still see them.
|
|
49
|
+
*/
|
|
50
|
+
export declare function contextEditor(options?: ContextEditorOptions): AgentMiddleware;
|
|
51
|
+
/** About four characters a token, over the messages, the tool calls, and the tool schemas. */
|
|
52
|
+
export declare function estimateRequestTokens(request: CompletionRequest): number;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { textOf } from './shared.js';
|
|
2
|
+
export function contextEditor(options = {}) {
|
|
3
|
+
const triggerTokens = options.triggerTokens ?? 30_000;
|
|
4
|
+
const keep = Math.max(0, options.keepToolResults ?? 3);
|
|
5
|
+
const batch = Math.max(1, options.batch ?? 5);
|
|
6
|
+
const exclude = new Set(options.exclude ?? []);
|
|
7
|
+
const estimate = options.estimateTokens ?? estimateRequestTokens;
|
|
8
|
+
const placeholder = (cleared) => typeof options.placeholder === 'function'
|
|
9
|
+
? options.placeholder(cleared)
|
|
10
|
+
: (options.placeholder ??
|
|
11
|
+
`[Cleared to save context: the result of ${cleared.tool}, ${cleared.length} characters. Call the tool again if you need it.]`);
|
|
12
|
+
return {
|
|
13
|
+
name: 'context-editor',
|
|
14
|
+
beforeModel({ request }) {
|
|
15
|
+
const tokensBefore = estimate(request);
|
|
16
|
+
if (tokensBefore <= triggerTokens)
|
|
17
|
+
return;
|
|
18
|
+
const names = new Map();
|
|
19
|
+
for (const message of request.messages) {
|
|
20
|
+
for (const call of message.toolCalls ?? [])
|
|
21
|
+
names.set(call.id, call.function.name);
|
|
22
|
+
}
|
|
23
|
+
const results = request.messages.flatMap((message, index) => (message.role === 'tool' ? [index] : []));
|
|
24
|
+
const eligible = Math.max(0, results.length - keep);
|
|
25
|
+
const count = eligible < batch ? eligible : eligible - (eligible % batch);
|
|
26
|
+
const clearable = new Set(results.slice(0, count).filter((index) => {
|
|
27
|
+
const id = request.messages[index]?.toolCallId;
|
|
28
|
+
return !exclude.has(names.get(id ?? '') ?? '');
|
|
29
|
+
}));
|
|
30
|
+
if (clearable.size === 0)
|
|
31
|
+
return;
|
|
32
|
+
const clearedIds = new Set();
|
|
33
|
+
const messages = request.messages.map((message, index) => {
|
|
34
|
+
if (!clearable.has(index))
|
|
35
|
+
return message;
|
|
36
|
+
const text = textOf(message.content);
|
|
37
|
+
const tool = names.get(message.toolCallId ?? '') ?? 'a tool';
|
|
38
|
+
clearedIds.add(message.toolCallId ?? '');
|
|
39
|
+
return { ...message, content: placeholder({ tool, length: text.length }) };
|
|
40
|
+
});
|
|
41
|
+
const edited = options.clearArguments
|
|
42
|
+
? messages.map((message) => message.toolCalls?.some((call) => clearedIds.has(call.id))
|
|
43
|
+
? {
|
|
44
|
+
...message,
|
|
45
|
+
toolCalls: message.toolCalls.map((call) => clearedIds.has(call.id) ? { ...call, function: { ...call.function, arguments: '{}' } } : call),
|
|
46
|
+
}
|
|
47
|
+
: message)
|
|
48
|
+
: messages;
|
|
49
|
+
const next = { ...request, messages: edited };
|
|
50
|
+
options.onEdit?.({ cleared: clearable.size, tokensBefore, tokensAfter: estimate(next) });
|
|
51
|
+
return next;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function estimateRequestTokens(request) {
|
|
56
|
+
let characters = 0;
|
|
57
|
+
for (const message of request.messages) {
|
|
58
|
+
characters += textOf(message.content).length + 4;
|
|
59
|
+
for (const call of message.toolCalls ?? [])
|
|
60
|
+
characters += call.function.name.length + call.function.arguments.length;
|
|
61
|
+
}
|
|
62
|
+
for (const tool of request.tools ?? []) {
|
|
63
|
+
characters += tool.name.length + tool.description.length + JSON.stringify(tool.parameters ?? {}).length;
|
|
64
|
+
}
|
|
65
|
+
return Math.ceil(characters / 4);
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AgentMiddleware } from '../create-agent.js';
|
|
2
|
+
import type { Sandbox } from '../sandbox.js';
|
|
3
|
+
/** Where `filesystemContext()` reads and writes: a `Sandbox`, or anything with the same file methods. */
|
|
4
|
+
export type FilesystemContextSource = Pick<Sandbox, 'readFile'> & Partial<Pick<Sandbox, 'writeFile' | 'listFiles'>>;
|
|
5
|
+
/** Where large tool results go instead of the transcript. */
|
|
6
|
+
export interface ContextOffloadOptions {
|
|
7
|
+
/** Results longer than this, in characters, are offloaded. Defaults to 20,000. */
|
|
8
|
+
overChars?: number;
|
|
9
|
+
/** The directory they are written to, inside the source. Defaults to `.context`. */
|
|
10
|
+
directory?: string;
|
|
11
|
+
/** How much of a result stays in the transcript as a preview, in characters. Defaults to 1,000. */
|
|
12
|
+
previewChars?: number;
|
|
13
|
+
/** Tools whose results are never offloaded. Defaults to `read_file`, so reading one back cannot loop. */
|
|
14
|
+
exclude?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/** Options for `filesystemContext()`. */
|
|
17
|
+
export interface FilesystemContextOptions {
|
|
18
|
+
/** Where files are read from and offloaded to. */
|
|
19
|
+
source: FilesystemContextSource;
|
|
20
|
+
/**
|
|
21
|
+
* Files whose contents the model sees on every call, after the system prompt: project
|
|
22
|
+
* instructions such as `AGENTS.md`, conventions, a glossary. A missing file is skipped.
|
|
23
|
+
*/
|
|
24
|
+
include?: readonly string[];
|
|
25
|
+
/** Lists a directory after the files, so the model knows what exists without a tool call. Needs `listFiles`. */
|
|
26
|
+
listing?: {
|
|
27
|
+
directory?: string;
|
|
28
|
+
maxEntries?: number;
|
|
29
|
+
};
|
|
30
|
+
/** The most characters of included files, together. Defaults to 32,000; the rest is cut. */
|
|
31
|
+
maxChars?: number;
|
|
32
|
+
/** Writes large tool results to a file and leaves a preview and the path. Needs `writeFile`. */
|
|
33
|
+
offload?: ContextOffloadOptions | boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The filesystem as the agent's context: files in, large results out.
|
|
37
|
+
*
|
|
38
|
+
* Instruction files reach the model on every call without a tool call, read once per run and placed
|
|
39
|
+
* right after the system prompt, so the prefix a provider caches stays the same. A tool result too
|
|
40
|
+
* large for the context is written to a file instead, and the model reads a preview and a path it can
|
|
41
|
+
* open with `read_file` when it needs the rest: the transcript stays small however much a tool
|
|
42
|
+
* returns, and nothing is lost.
|
|
43
|
+
*/
|
|
44
|
+
export declare function filesystemContext(options: FilesystemContextOptions): AgentMiddleware;
|