@dynamicagents/core 0.9.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/LICENSE +201 -0
- package/README.md +522 -0
- package/dist/a2a/agent-stub.d.ts +60 -0
- package/dist/a2a/agent-stub.js +1 -0
- package/dist/a2a/caller-token.d.ts +44 -0
- package/dist/a2a/caller-token.js +61 -0
- package/dist/a2a/caller.d.ts +23 -0
- package/dist/a2a/caller.js +33 -0
- package/dist/a2a/card.d.ts +158 -0
- package/dist/a2a/card.js +163 -0
- package/dist/a2a/context.d.ts +19 -0
- package/dist/a2a/context.js +61 -0
- package/dist/a2a/deliver.d.ts +159 -0
- package/dist/a2a/deliver.js +185 -0
- package/dist/a2a/executor.d.ts +84 -0
- package/dist/a2a/executor.js +106 -0
- package/dist/a2a/index.d.ts +35 -0
- package/dist/a2a/index.js +34 -0
- package/dist/a2a/notify.d.ts +106 -0
- package/dist/a2a/notify.js +180 -0
- package/dist/a2a/parts.d.ts +45 -0
- package/dist/a2a/parts.js +94 -0
- package/dist/a2a/push.d.ts +70 -0
- package/dist/a2a/push.js +53 -0
- package/dist/a2a/self-origin.d.ts +91 -0
- package/dist/a2a/self-origin.js +114 -0
- package/dist/a2a/task-store.d.ts +44 -0
- package/dist/a2a/task-store.js +99 -0
- package/dist/a2a/task.d.ts +85 -0
- package/dist/a2a/task.js +15 -0
- package/dist/a2a/verify.d.ts +80 -0
- package/dist/a2a/verify.js +143 -0
- package/dist/agent/budget.d.ts +46 -0
- package/dist/agent/budget.js +42 -0
- package/dist/agent/control.d.ts +109 -0
- package/dist/agent/control.js +115 -0
- package/dist/agent/errors.d.ts +85 -0
- package/dist/agent/errors.js +64 -0
- package/dist/agent/final-reply.d.ts +49 -0
- package/dist/agent/final-reply.js +68 -0
- package/dist/agent/history.d.ts +97 -0
- package/dist/agent/history.js +133 -0
- package/dist/agent/index.d.ts +29 -0
- package/dist/agent/index.js +29 -0
- package/dist/agent/inference.d.ts +110 -0
- package/dist/agent/inference.js +120 -0
- package/dist/agent/model.d.ts +90 -0
- package/dist/agent/model.js +1 -0
- package/dist/agent/session.d.ts +100 -0
- package/dist/agent/session.js +82 -0
- package/dist/agent/workers-ai/index.d.ts +23 -0
- package/dist/agent/workers-ai/index.js +23 -0
- package/dist/agent/workers-ai/runtime.d.ts +42 -0
- package/dist/agent/workers-ai/runtime.js +63 -0
- package/dist/alarm/index.d.ts +77 -0
- package/dist/alarm/index.js +116 -0
- package/dist/config.d.ts +202 -0
- package/dist/config.js +135 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +8 -0
- package/dist/contract/plugin.d.ts +324 -0
- package/dist/contract/plugin.js +114 -0
- package/dist/contract/recipe.d.ts +180 -0
- package/dist/contract/recipe.js +1 -0
- package/dist/contract/validation.d.ts +91 -0
- package/dist/contract/validation.js +84 -0
- package/dist/db/db.d.ts +147 -0
- package/dist/db/db.js +90 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +8 -0
- package/dist/db/migrations/index.d.ts +20 -0
- package/dist/db/migrations/index.js +60 -0
- package/dist/db/models/subtasks.d.ts +100 -0
- package/dist/db/models/subtasks.js +241 -0
- package/dist/db/models/tasks.d.ts +118 -0
- package/dist/db/models/tasks.js +274 -0
- package/dist/db/schema.d.ts +468 -0
- package/dist/db/schema.js +88 -0
- package/dist/env.d.ts +53 -0
- package/dist/env.js +47 -0
- package/dist/host/agent.d.ts +305 -0
- package/dist/host/agent.js +400 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/index.js +19 -0
- package/dist/host/plugin-host.d.ts +42 -0
- package/dist/host/plugin-host.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +17 -0
- package/dist/job/index.d.ts +21 -0
- package/dist/job/index.js +21 -0
- package/dist/job/lifecycle.d.ts +176 -0
- package/dist/job/lifecycle.js +230 -0
- package/dist/job/state.d.ts +92 -0
- package/dist/job/state.js +40 -0
- package/dist/platform.d.ts +138 -0
- package/dist/platform.js +140 -0
- package/dist/round/agent.d.ts +271 -0
- package/dist/round/agent.js +678 -0
- package/dist/round/index.d.ts +25 -0
- package/dist/round/index.js +23 -0
- package/dist/round/policy.d.ts +98 -0
- package/dist/round/policy.js +1 -0
- package/dist/round/subagent.d.ts +87 -0
- package/dist/round/subagent.js +108 -0
- package/dist/round/turn.d.ts +249 -0
- package/dist/round/turn.js +564 -0
- package/dist/round/workflow.d.ts +147 -0
- package/dist/round/workflow.js +387 -0
- package/dist/runtime/index.d.ts +127 -0
- package/dist/runtime/index.js +186 -0
- package/dist/runtime/tool-families.d.ts +32 -0
- package/dist/runtime/tool-families.js +61 -0
- package/dist/subagent/fingerprint.d.ts +37 -0
- package/dist/subagent/fingerprint.js +92 -0
- package/dist/subagent/index.d.ts +169 -0
- package/dist/subagent/index.js +330 -0
- package/dist/subagent/prompt.d.ts +32 -0
- package/dist/subagent/prompt.js +37 -0
- package/dist/subagent/run.d.ts +157 -0
- package/dist/subagent/run.js +540 -0
- package/dist/subagent/workspace.d.ts +85 -0
- package/dist/subagent/workspace.js +127 -0
- package/dist/subtasks/catalog.d.ts +40 -0
- package/dist/subtasks/catalog.js +36 -0
- package/dist/subtasks/decomposition.d.ts +85 -0
- package/dist/subtasks/decomposition.js +156 -0
- package/dist/subtasks/delegate.d.ts +120 -0
- package/dist/subtasks/delegate.js +131 -0
- package/dist/subtasks/index.d.ts +9 -0
- package/dist/subtasks/index.js +9 -0
- package/dist/subtasks/subtask-types.d.ts +91 -0
- package/dist/subtasks/subtask-types.js +103 -0
- package/dist/subtasks/types.d.ts +295 -0
- package/dist/subtasks/types.js +15 -0
- package/dist/testing/auth.d.ts +34 -0
- package/dist/testing/auth.js +35 -0
- package/dist/testing/do.d.ts +29 -0
- package/dist/testing/do.js +25 -0
- package/dist/testing/fake-session.d.ts +26 -0
- package/dist/testing/fake-session.js +37 -0
- package/dist/testing/fixtures.d.ts +64 -0
- package/dist/testing/fixtures.js +104 -0
- package/dist/testing/harness.d.ts +97 -0
- package/dist/testing/harness.js +138 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.js +35 -0
- package/dist/testing/mock-model.d.ts +77 -0
- package/dist/testing/mock-model.js +136 -0
- package/dist/testing/node.d.ts +56 -0
- package/dist/testing/node.js +56 -0
- package/dist/testing/vcr-global-setup.d.ts +12 -0
- package/dist/testing/vcr-global-setup.js +15 -0
- package/dist/testing/vcr-shared.d.ts +38 -0
- package/dist/testing/vcr-shared.js +33 -0
- package/dist/testing/vcr-spec.d.ts +25 -0
- package/dist/testing/vcr-spec.js +124 -0
- package/dist/testing/vcr-store.d.ts +86 -0
- package/dist/testing/vcr-store.js +191 -0
- package/dist/testing/vcr.d.ts +117 -0
- package/dist/testing/vcr.js +275 -0
- package/dist/worker/define-agent.d.ts +123 -0
- package/dist/worker/define-agent.js +20 -0
- package/dist/worker/index.d.ts +218 -0
- package/dist/worker/index.js +369 -0
- package/eslint-rules/index.js +31 -0
- package/eslint-rules/no-deprecated-object-properties.js +81 -0
- package/package.json +178 -0
- package/scripts/generate-keys.mjs +48 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const TURN_TAG_RE = /^<turn\b([^>]*)>([\s\S]*)<\/turn>$/;
|
|
2
|
+
const ATTR_RE = /(\w+)="([^"]*)"/g;
|
|
3
|
+
function parseAttrs(raw) {
|
|
4
|
+
const out = {};
|
|
5
|
+
let m;
|
|
6
|
+
ATTR_RE.lastIndex = 0;
|
|
7
|
+
while ((m = ATTR_RE.exec(raw)) !== null)
|
|
8
|
+
out[m[1]] = m[2];
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
const ATTR_UNESCAPES = {
|
|
12
|
+
amp: "&",
|
|
13
|
+
lt: "<",
|
|
14
|
+
gt: ">",
|
|
15
|
+
quot: '"'
|
|
16
|
+
};
|
|
17
|
+
/** Reverse the gatekeeper's attribute escaping — single pass so `&` round-trips. */
|
|
18
|
+
function unescAttr(value) {
|
|
19
|
+
return value.replace(/&(amp|lt|gt|quot);/g, (_, e) => ATTR_UNESCAPES[e]);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Recover the structured provenance from a gatekeeper-authored turn. Returns null
|
|
23
|
+
* for any text that isn't a `<turn>` wrapper (plain messages, assistant replies),
|
|
24
|
+
* so callers can treat the provenance as optional.
|
|
25
|
+
*/
|
|
26
|
+
export function parseTurn(text) {
|
|
27
|
+
const m = TURN_TAG_RE.exec(text);
|
|
28
|
+
if (!m)
|
|
29
|
+
return null;
|
|
30
|
+
const attrs = parseAttrs(m[1]);
|
|
31
|
+
if (!attrs.from || !attrs.id || !attrs.channel || !attrs.at)
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
from: unescAttr(attrs.from),
|
|
35
|
+
id: unescAttr(attrs.id),
|
|
36
|
+
channel: unescAttr(attrs.channel),
|
|
37
|
+
at: unescAttr(attrs.at),
|
|
38
|
+
body: m[2]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Deterministic Session-message ids for the task round loop.
|
|
43
|
+
*
|
|
44
|
+
* A round runs inside a durable Workflow step that can re-run after a crash, so
|
|
45
|
+
* its Session appends must be exactly-once. `Session.appendMessage` already
|
|
46
|
+
* dedupes on message id (an id that exists is not re-written), so deriving the id
|
|
47
|
+
* from the task id + round makes the append idempotent for free — no
|
|
48
|
+
* read-then-write race, no duplicate turns in history on replay. Pair with
|
|
49
|
+
* {@link file://./session.ts appendOnce}, which reads the durable text back so a
|
|
50
|
+
* retry that re-inferred still returns the *stored* reply.
|
|
51
|
+
*
|
|
52
|
+
* These ids share the Session id-space with random UUIDs (plain turns) and the
|
|
53
|
+
* SDK's `compaction_`-prefixed summaries; the `task:` prefix cannot collide with
|
|
54
|
+
* either.
|
|
55
|
+
*/
|
|
56
|
+
/** Id of the inbound user turn for a task (appended once, by round 0). */
|
|
57
|
+
export function taskUserMessageId(taskId) {
|
|
58
|
+
return `task:${taskId}:user`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Id of the acknowledgment a **delegating** round publishes — the message the
|
|
62
|
+
* user sees while that round's Subtasks run. Also the anchor a later round finds
|
|
63
|
+
* to reattach the round's `delegate` call to (see
|
|
64
|
+
* {@link file://../round/turn.ts renderTurnMessages}), which is why it is
|
|
65
|
+
* derived from the round rather than stored.
|
|
66
|
+
*/
|
|
67
|
+
export function roundAckMessageId(taskId, round) {
|
|
68
|
+
return `task:${taskId}:round:${round}:ack`;
|
|
69
|
+
}
|
|
70
|
+
/** Matches {@link roundAckMessageId}; the trailing suffix is fixed, so the greedy
|
|
71
|
+
* task-id group cannot swallow it. */
|
|
72
|
+
const ROUND_ACK_ID = /^task:(.+):round:(\d+):ack$/;
|
|
73
|
+
/**
|
|
74
|
+
* Recognize an acknowledgment id — **any** Task's, not just the one being
|
|
75
|
+
* rendered. A Session outlives the Task that wrote it and is shared by every Task
|
|
76
|
+
* from the same caller, so its history accumulates acks the current render has no
|
|
77
|
+
* branches for: earlier Tasks' acks, and (in the window between a round's ack
|
|
78
|
+
* append and its Subtask rows) this Task's own. Both are the agent's scaffolding
|
|
79
|
+
* rather than conversation, so both must stay out of the reference catalog; the
|
|
80
|
+
* `branches`-derived anchor map alone cannot see either.
|
|
81
|
+
*
|
|
82
|
+
* Returns the parsed Task id and round so the caller can tell whose ack it is.
|
|
83
|
+
*/
|
|
84
|
+
export function parseRoundAckMessageId(id) {
|
|
85
|
+
const match = ROUND_ACK_ID.exec(id);
|
|
86
|
+
return match ? { taskId: match[1], round: Number(match[2]) } : null;
|
|
87
|
+
}
|
|
88
|
+
/** Id of the terminal reply — the round in which the agent answered the user. */
|
|
89
|
+
export function finalReplyMessageId(taskId) {
|
|
90
|
+
return `task:${taskId}:reply:final`;
|
|
91
|
+
}
|
|
92
|
+
/** A Sessions-store message with a caller-chosen (deterministic) id. */
|
|
93
|
+
export function deterministicSessionMessage(id, role, text) {
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
role,
|
|
97
|
+
createdAt: new Date(),
|
|
98
|
+
parts: [{ type: "text", text }]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A Sessions-store message with a fresh random id.
|
|
103
|
+
*
|
|
104
|
+
* The counterpart to {@link deterministicSessionMessage}, and choosing between
|
|
105
|
+
* them is a real decision rather than a style one. A deterministic id makes an
|
|
106
|
+
* append **idempotent**, which is what a durable-Workflow agent needs: its round
|
|
107
|
+
* can re-run after a crash, and the id is what stops the retry from duplicating a
|
|
108
|
+
* turn or rewriting a reply the user already received.
|
|
109
|
+
*
|
|
110
|
+
* A conversational agent that answers inline has no such replay to defend
|
|
111
|
+
* against — every turn is a new message and there is no step to re-enter — so a
|
|
112
|
+
* random id is correct and a synthesized deterministic one would be a lie about
|
|
113
|
+
* what is being deduplicated.
|
|
114
|
+
*/
|
|
115
|
+
export function sessionMessage(role, text) {
|
|
116
|
+
return deterministicSessionMessage(crypto.randomUUID(), role, text);
|
|
117
|
+
}
|
|
118
|
+
/** Concatenate the text parts of a stored session message. */
|
|
119
|
+
export function sessionText(m) {
|
|
120
|
+
return m.parts
|
|
121
|
+
.filter((p) => p.type === "text" && typeof p.text === "string")
|
|
122
|
+
.map((p) => p.text)
|
|
123
|
+
.join("");
|
|
124
|
+
}
|
|
125
|
+
/** Convert stored history to AI-SDK model messages (user/assistant text only). */
|
|
126
|
+
export function toModelMessages(history) {
|
|
127
|
+
return history
|
|
128
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
129
|
+
.map((m) => ({
|
|
130
|
+
role: m.role,
|
|
131
|
+
content: sessionText(m)
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/agent` — the primitives a loop is built from.
|
|
3
|
+
*
|
|
4
|
+
* The session, the model pair with its fallback, the budget, and the
|
|
5
|
+
* control-tool abstraction that turns "the model called something that ends the
|
|
6
|
+
* round" into a checked value. Everything both predecessor loops needed
|
|
7
|
+
* identically, and nothing about how a round is shaped.
|
|
8
|
+
*
|
|
9
|
+
* This subpath ships **no loop** — that is what makes it importable by one. A
|
|
10
|
+
* loop module needs these primitives without also pulling in a Durable Object
|
|
11
|
+
* base class and drizzle, which is why `DynamicAgent` lives in
|
|
12
|
+
* `@dynamicagents/core/host` and the delegating round loop in
|
|
13
|
+
* `@dynamicagents/core/round`.
|
|
14
|
+
*
|
|
15
|
+
* Core as a whole *does* now ship a loop, opt-in, in `/round`. The rule it
|
|
16
|
+
* still keeps is narrower and better: **core ships no prompt copy and no
|
|
17
|
+
* policy** — see `AGENTS.md`, "The line core does not cross". An agent that
|
|
18
|
+
* wants a different round shape imports none of `/round` and builds it from
|
|
19
|
+
* exactly what is here.
|
|
20
|
+
*/
|
|
21
|
+
export { newTurnBudget, stepAllowance, type TurnBudget } from "./budget.js";
|
|
22
|
+
export { type AiGatewayMetadata, type ModelOverrides, type ModelPair, type ModelRuntime, type ModelRuntimeFactory } from "./model.js";
|
|
23
|
+
export { CredentialRejectedError, type CredentialRejectedBy } from "./errors.js";
|
|
24
|
+
export { createWorkersAIModelRuntime, workersAIModels, type WorkersAIRuntimeDeps } from "./workers-ai/index.js";
|
|
25
|
+
export { appendOnce, buildAgentSession, notifyingCompaction, type AgentSessionOptions, type SessionHost, type SessionLike } from "./session.js";
|
|
26
|
+
export { deterministicSessionMessage, finalReplyMessageId, parseRoundAckMessageId, parseTurn, roundAckMessageId, sessionMessage, sessionText, taskUserMessageId, toModelMessages, type ParsedTurn } from "./history.js";
|
|
27
|
+
export { buildIntermediateContentHandler, isTransientAiError, nonRecoverableKind, type NonRecoverableKind, type OnContent, type RoundFailureKind } from "./inference.js";
|
|
28
|
+
export { ControlCallError, controlTools, controlToolSet, type ControlTool, type TurnDecision } from "./control.js";
|
|
29
|
+
export { FINAL_REPLY_TOOL_NAME, finalReplyInputSchema, finalReplyTool } from "./final-reply.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/agent` — the primitives a loop is built from.
|
|
3
|
+
*
|
|
4
|
+
* The session, the model pair with its fallback, the budget, and the
|
|
5
|
+
* control-tool abstraction that turns "the model called something that ends the
|
|
6
|
+
* round" into a checked value. Everything both predecessor loops needed
|
|
7
|
+
* identically, and nothing about how a round is shaped.
|
|
8
|
+
*
|
|
9
|
+
* This subpath ships **no loop** — that is what makes it importable by one. A
|
|
10
|
+
* loop module needs these primitives without also pulling in a Durable Object
|
|
11
|
+
* base class and drizzle, which is why `DynamicAgent` lives in
|
|
12
|
+
* `@dynamicagents/core/host` and the delegating round loop in
|
|
13
|
+
* `@dynamicagents/core/round`.
|
|
14
|
+
*
|
|
15
|
+
* Core as a whole *does* now ship a loop, opt-in, in `/round`. The rule it
|
|
16
|
+
* still keeps is narrower and better: **core ships no prompt copy and no
|
|
17
|
+
* policy** — see `AGENTS.md`, "The line core does not cross". An agent that
|
|
18
|
+
* wants a different round shape imports none of `/round` and builds it from
|
|
19
|
+
* exactly what is here.
|
|
20
|
+
*/
|
|
21
|
+
export { newTurnBudget, stepAllowance } from "./budget.js";
|
|
22
|
+
export {} from "./model.js";
|
|
23
|
+
export { CredentialRejectedError } from "./errors.js";
|
|
24
|
+
export { createWorkersAIModelRuntime, workersAIModels } from "./workers-ai/index.js";
|
|
25
|
+
export { appendOnce, buildAgentSession, notifyingCompaction } from "./session.js";
|
|
26
|
+
export { deterministicSessionMessage, finalReplyMessageId, parseRoundAckMessageId, parseTurn, roundAckMessageId, sessionMessage, sessionText, taskUserMessageId, toModelMessages } from "./history.js";
|
|
27
|
+
export { buildIntermediateContentHandler, isTransientAiError, nonRecoverableKind } from "./inference.js";
|
|
28
|
+
export { ControlCallError, controlTools, controlToolSet } from "./control.js";
|
|
29
|
+
export { FINAL_REPLY_TOOL_NAME, finalReplyInputSchema, finalReplyTool } from "./final-reply.js";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { StepResult, ToolSet } from "ai";
|
|
2
|
+
/**
|
|
3
|
+
* Shared Workers-AI plumbing for the agent's inference operations — the pieces
|
|
4
|
+
* every model call needs regardless of *which* operation it belongs to.
|
|
5
|
+
*
|
|
6
|
+
* The two loops themselves are deliberately separate, not layered on a common
|
|
7
|
+
* one: the main agent's Session-coupled round lives in
|
|
8
|
+
* {@link file://../round/turn.ts turn.ts}, and the Session-less subagent loop in
|
|
9
|
+
* {@link file://../subagent/run.ts run.ts}. They share error classification and
|
|
10
|
+
* progress streaming; their control flow has nothing in common worth abstracting.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Called with each **intermediate** assistant content message — text the model
|
|
14
|
+
* emits in a step that also makes tool calls (`finishReason:"tool-calls"`), i.e.
|
|
15
|
+
* before the final reply. Used to stream those messages out live; the final reply
|
|
16
|
+
* is the operation's return value, not an `onContent` call. `stepIndex` is the
|
|
17
|
+
* 0-based step ordinal (stable enough across a primary→fallback re-run for the
|
|
18
|
+
* gatekeeper to dedupe on). Best-effort — the caller must swallow its own failures.
|
|
19
|
+
*/
|
|
20
|
+
export type OnContent = (text: string, stepIndex: number) => void | Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Whether an error is a transient availability condition rather than a
|
|
23
|
+
* deterministic bad-output one.
|
|
24
|
+
*
|
|
25
|
+
* The distinction decides who handles it: transient throws out of the attempt loop
|
|
26
|
+
* so the Workflow step retries the whole round, while everything else burns the
|
|
27
|
+
* model slot and hands over to the fallback. Classifying a capacity blip as
|
|
28
|
+
* deterministic is the expensive mistake — it spends both slots on an outage and
|
|
29
|
+
* fails a Task that would have succeeded a second later.
|
|
30
|
+
*
|
|
31
|
+
* Structured signals first: the SDK's own `APICallError.isRetryable`, then the
|
|
32
|
+
* status code, then `RetryError` (raised once the SDK's internal backoff is
|
|
33
|
+
* exhausted). The message fragments stay as the last resort for the Workers-AI
|
|
34
|
+
* error codes, which arrive as prose on a plain `Error`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isTransientAiError(err: unknown): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Why a round stopped without a second attempt being worth making.
|
|
39
|
+
*
|
|
40
|
+
* A stable string rather than the error itself, because this value crosses two
|
|
41
|
+
* serialization boundaries — the DO's RPC return and a Workflow step result —
|
|
42
|
+
* and an `Error` survives neither reliably. The host maps it to operator-facing
|
|
43
|
+
* copy; core never owns that wording.
|
|
44
|
+
*
|
|
45
|
+
* The credential kinds are separate strings rather than one, because they have
|
|
46
|
+
* different remedies and the host cannot tell them apart afterwards:
|
|
47
|
+
*
|
|
48
|
+
* - `credential` — the model provider rejected the token. Rotate that one.
|
|
49
|
+
* - `gateway-credential` — the AI Gateway *in front of* the provider rejected the
|
|
50
|
+
* request, which the provider therefore never saw. Rotate the AI Gateway token
|
|
51
|
+
* (`cf-aig-authorization`) instead; the model credential is very likely fine.
|
|
52
|
+
* - `unknown-credential` — a `401`/`403` matching none of the shapes. Says so,
|
|
53
|
+
* rather than picking one and sending an operator to rotate a working secret.
|
|
54
|
+
*
|
|
55
|
+
* A fourth, `proxy-credential`, was removed in 0.8.0 along with
|
|
56
|
+
* {@link file://./errors.ts CredentialRejectedBy}'s `"proxy"` arm. Adding a kind
|
|
57
|
+
* back is a breaking change for every consumer, because the `Record` they map it
|
|
58
|
+
* with is total — which is the property that makes a new kind impossible to
|
|
59
|
+
* ignore, and the reason to remove one rather than leave it unreachable.
|
|
60
|
+
*/
|
|
61
|
+
export type NonRecoverableKind = "credential" | "gateway-credential" | "unknown-credential";
|
|
62
|
+
/**
|
|
63
|
+
* Why a round ended with no answer — one terminal status, two situations.
|
|
64
|
+
*
|
|
65
|
+
* `exhausted` is the ladder run to the end: both slots tried, every repair
|
|
66
|
+
* spent, nothing usable produced. Every other member is the ladder stopping
|
|
67
|
+
* early, because nothing further could have cleared the fault — see
|
|
68
|
+
* {@link nonRecoverableKind}.
|
|
69
|
+
*
|
|
70
|
+
* The distinction is a *reason*, not an outcome: both deliver a failed Task with
|
|
71
|
+
* the same shape. What it decides is the words, and only the host has those (see
|
|
72
|
+
* `HandleTaskDeps.failureCopy`) — which is why this is a total union rather than
|
|
73
|
+
* an optional field. A consumer that maps kinds to copy is then a `Record` the
|
|
74
|
+
* compiler checks, and a new kind cannot be silently ignored by any of them.
|
|
75
|
+
*/
|
|
76
|
+
export type RoundFailureKind = "exhausted" | NonRecoverableKind;
|
|
77
|
+
/**
|
|
78
|
+
* Whether an error is one that **no** further attempt can clear, and the reason.
|
|
79
|
+
*
|
|
80
|
+
* This is the third classification, and the one the other two cannot express.
|
|
81
|
+
* {@link isTransientAiError} splits failures into "retry the step" (`true`) and
|
|
82
|
+
* "burn this slot, try the fallback" (`false`) — and for a rejected credential
|
|
83
|
+
* *both* are wrong. Retrying spends the Workflow's budget on a request that can
|
|
84
|
+
* never succeed; falling back spends the second slot presenting the *same* dead
|
|
85
|
+
* token. Returning `false` from the transient check only avoids the first.
|
|
86
|
+
*
|
|
87
|
+
* So the attempt ladders check this **before** entering the fallback slot and
|
|
88
|
+
* stop there, and `runHandleTask` ends the Task with copy the host supplies.
|
|
89
|
+
* Nothing is retried and nothing is spent proving the obvious twice.
|
|
90
|
+
*
|
|
91
|
+
* Keyed on {@link file://./errors.ts CredentialRejectedError}, which is neutral
|
|
92
|
+
* and structurally matched — so a provider outside core raises one and gets this
|
|
93
|
+
* handling with nothing here to change.
|
|
94
|
+
*/
|
|
95
|
+
export declare function nonRecoverableKind(err: unknown): NonRecoverableKind | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Returns a fresh `onStepEnd` callback for one `generateText` attempt.
|
|
98
|
+
* Fires `onContent` for each intermediate step (text that accompanies tool
|
|
99
|
+
* calls); the final step is skipped because its text is the operation's return
|
|
100
|
+
* value. A fresh handler per attempt resets the 0-based `stepIndex` counter so a
|
|
101
|
+
* primary→fallback re-run reuses the same indices and the gatekeeper dedupes.
|
|
102
|
+
*
|
|
103
|
+
* `terminalToolNames` are the loop's **halting** control tools (e.g. the main
|
|
104
|
+
* agent's `delegate`): a step that calls one still has `finishReason:"tool-calls"`,
|
|
105
|
+
* but it is the round's *final* step, and its accompanying text is the round's
|
|
106
|
+
* acknowledgment — which the caller publishes separately as a milestone. Streaming
|
|
107
|
+
* it here too would double-post the same text under a second messageId, so those
|
|
108
|
+
* steps are skipped. Default `[]` (the subagent loop has no control tools).
|
|
109
|
+
*/
|
|
110
|
+
export declare function buildIntermediateContentHandler(onContent: OnContent, terminalToolNames?: string[]): (step: StepResult<ToolSet>) => Promise<void>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { APICallError, RetryError } from "ai";
|
|
2
|
+
// Type-only would not work: this is a runtime guard. `errors.ts` is the neutral
|
|
3
|
+
// sibling of `model.ts` and imports nothing, so this reaches no provider.
|
|
4
|
+
import { CredentialRejectedError } from "./errors.js";
|
|
5
|
+
/** Workers-AI error codes and message fragments that mean "try again later". */
|
|
6
|
+
const TRANSIENT_MESSAGE_FRAGMENTS = [
|
|
7
|
+
"3040",
|
|
8
|
+
"3046",
|
|
9
|
+
"capacity temporarily exceeded",
|
|
10
|
+
"request timeout",
|
|
11
|
+
"rate limit",
|
|
12
|
+
"too many requests",
|
|
13
|
+
"overloaded",
|
|
14
|
+
"service unavailable"
|
|
15
|
+
];
|
|
16
|
+
/** HTTP statuses worth another attempt: timeout, conflict, throttle, any 5xx. */
|
|
17
|
+
function isRetryableStatus(status) {
|
|
18
|
+
if (status === undefined)
|
|
19
|
+
return false;
|
|
20
|
+
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether an error is a transient availability condition rather than a
|
|
24
|
+
* deterministic bad-output one.
|
|
25
|
+
*
|
|
26
|
+
* The distinction decides who handles it: transient throws out of the attempt loop
|
|
27
|
+
* so the Workflow step retries the whole round, while everything else burns the
|
|
28
|
+
* model slot and hands over to the fallback. Classifying a capacity blip as
|
|
29
|
+
* deterministic is the expensive mistake — it spends both slots on an outage and
|
|
30
|
+
* fails a Task that would have succeeded a second later.
|
|
31
|
+
*
|
|
32
|
+
* Structured signals first: the SDK's own `APICallError.isRetryable`, then the
|
|
33
|
+
* status code, then `RetryError` (raised once the SDK's internal backoff is
|
|
34
|
+
* exhausted). The message fragments stay as the last resort for the Workers-AI
|
|
35
|
+
* error codes, which arrive as prose on a plain `Error`.
|
|
36
|
+
*/
|
|
37
|
+
export function isTransientAiError(err) {
|
|
38
|
+
// Checked first because a rejected credential's message can carry "rate
|
|
39
|
+
// limit"-adjacent prose the fragment scan below would misread as transient.
|
|
40
|
+
// Note that `false` alone does not protect the fallback slot — see
|
|
41
|
+
// {@link nonRecoverableKind}, which is what actually stops the ladder.
|
|
42
|
+
if (nonRecoverableKind(err) !== undefined)
|
|
43
|
+
return false;
|
|
44
|
+
if (APICallError.isInstance(err)) {
|
|
45
|
+
if (err.isRetryable)
|
|
46
|
+
return true;
|
|
47
|
+
if (isRetryableStatus(err.statusCode))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (RetryError.isInstance(err))
|
|
51
|
+
return true;
|
|
52
|
+
if (!(err instanceof Error))
|
|
53
|
+
return false;
|
|
54
|
+
const message = err.message.toLowerCase();
|
|
55
|
+
return TRANSIENT_MESSAGE_FRAGMENTS.some((fragment) => message.includes(fragment));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether an error is one that **no** further attempt can clear, and the reason.
|
|
59
|
+
*
|
|
60
|
+
* This is the third classification, and the one the other two cannot express.
|
|
61
|
+
* {@link isTransientAiError} splits failures into "retry the step" (`true`) and
|
|
62
|
+
* "burn this slot, try the fallback" (`false`) — and for a rejected credential
|
|
63
|
+
* *both* are wrong. Retrying spends the Workflow's budget on a request that can
|
|
64
|
+
* never succeed; falling back spends the second slot presenting the *same* dead
|
|
65
|
+
* token. Returning `false` from the transient check only avoids the first.
|
|
66
|
+
*
|
|
67
|
+
* So the attempt ladders check this **before** entering the fallback slot and
|
|
68
|
+
* stop there, and `runHandleTask` ends the Task with copy the host supplies.
|
|
69
|
+
* Nothing is retried and nothing is spent proving the obvious twice.
|
|
70
|
+
*
|
|
71
|
+
* Keyed on {@link file://./errors.ts CredentialRejectedError}, which is neutral
|
|
72
|
+
* and structurally matched — so a provider outside core raises one and gets this
|
|
73
|
+
* handling with nothing here to change.
|
|
74
|
+
*/
|
|
75
|
+
export function nonRecoverableKind(err) {
|
|
76
|
+
if (!CredentialRejectedError.isInstance(err))
|
|
77
|
+
return undefined;
|
|
78
|
+
switch (err.source) {
|
|
79
|
+
case "provider":
|
|
80
|
+
return "credential";
|
|
81
|
+
case "gateway":
|
|
82
|
+
return "gateway-credential";
|
|
83
|
+
// Includes an error that crossed a realm boundary carrying no `source` at
|
|
84
|
+
// all: `isInstance` is structural, so that is reachable, and "unknown" is
|
|
85
|
+
// the honest reading of it.
|
|
86
|
+
default:
|
|
87
|
+
return "unknown-credential";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** A step is "intermediate" when it makes tool calls — more content follows. */
|
|
91
|
+
function isIntermediateStep(step) {
|
|
92
|
+
return step.finishReason === "tool-calls";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns a fresh `onStepEnd` callback for one `generateText` attempt.
|
|
96
|
+
* Fires `onContent` for each intermediate step (text that accompanies tool
|
|
97
|
+
* calls); the final step is skipped because its text is the operation's return
|
|
98
|
+
* value. A fresh handler per attempt resets the 0-based `stepIndex` counter so a
|
|
99
|
+
* primary→fallback re-run reuses the same indices and the gatekeeper dedupes.
|
|
100
|
+
*
|
|
101
|
+
* `terminalToolNames` are the loop's **halting** control tools (e.g. the main
|
|
102
|
+
* agent's `delegate`): a step that calls one still has `finishReason:"tool-calls"`,
|
|
103
|
+
* but it is the round's *final* step, and its accompanying text is the round's
|
|
104
|
+
* acknowledgment — which the caller publishes separately as a milestone. Streaming
|
|
105
|
+
* it here too would double-post the same text under a second messageId, so those
|
|
106
|
+
* steps are skipped. Default `[]` (the subagent loop has no control tools).
|
|
107
|
+
*/
|
|
108
|
+
export function buildIntermediateContentHandler(onContent, terminalToolNames = []) {
|
|
109
|
+
let stepIndex = 0;
|
|
110
|
+
return async (step) => {
|
|
111
|
+
const i = stepIndex++;
|
|
112
|
+
if (!isIntermediateStep(step))
|
|
113
|
+
return;
|
|
114
|
+
if (step.toolCalls.some((c) => terminalToolNames.includes(c.toolName)))
|
|
115
|
+
return;
|
|
116
|
+
const content = step.text.trim();
|
|
117
|
+
if (content)
|
|
118
|
+
await onContent(content, i);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
import type { ModelConfig } from "../config.js";
|
|
3
|
+
/**
|
|
4
|
+
* The provider contract every loop runs against — and nothing that implements
|
|
5
|
+
* it.
|
|
6
|
+
*
|
|
7
|
+
* {@link ModelRuntime} is the whole seam: an agent that returns one runs every
|
|
8
|
+
* loop in core unchanged, because nothing downstream — the round loop, the
|
|
9
|
+
* control-tool repair ladder, the Session's compaction, the Workflow — ever sees
|
|
10
|
+
* more than a `LanguageModel` from `ai`. It never learns which provider produced
|
|
11
|
+
* it.
|
|
12
|
+
*
|
|
13
|
+
* Core ships exactly one implementation —
|
|
14
|
+
* {@link file://./workers-ai/index.ts `./workers-ai`} — as a sibling directory
|
|
15
|
+
* under `agent/`. A second provider is a second directory exporting one
|
|
16
|
+
* {@link ModelRuntimeFactory}, and nothing here has to change to admit it;
|
|
17
|
+
* a consumer can equally supply one from outside the package, which is the
|
|
18
|
+
* point of the seam.
|
|
19
|
+
*
|
|
20
|
+
* There was a `./anthropic` sibling until 0.8.0 — a hand-written Messages API
|
|
21
|
+
* adapter behind an optional peer dependency. It was removed with the only
|
|
22
|
+
* deployment that used it. Nothing about this contract changed when it went,
|
|
23
|
+
* which is the strongest thing that can be said for the contract.
|
|
24
|
+
*
|
|
25
|
+
* Which is why this file has no runtime imports at all. The Workers AI factory
|
|
26
|
+
* used to live in it, and a contract that ships one implementation inline reads
|
|
27
|
+
* as *the* runtime with an escape hatch, rather than as one of N.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Custom metadata attached to the AI Gateway log for every call a pair makes.
|
|
31
|
+
* AI Gateway's own `metadata` is otherwise `null`, so a model call can only be
|
|
32
|
+
* tied back to its task by timestamp; stamping `{ taskId, round }` (a turn) or
|
|
33
|
+
* `{ taskId, subtaskId }` (a chunk) makes correlation exact. Values are limited
|
|
34
|
+
* to AI Gateway's accepted scalar set.
|
|
35
|
+
*/
|
|
36
|
+
export type AiGatewayMetadata = Record<string, number | string | boolean>;
|
|
37
|
+
export interface ModelOverrides {
|
|
38
|
+
/** Test override for the primary slot. */
|
|
39
|
+
model?: LanguageModel;
|
|
40
|
+
/** Test override for the fallback slot. */
|
|
41
|
+
fallbackModel?: LanguageModel;
|
|
42
|
+
/**
|
|
43
|
+
* The provider's model id for the primary slot. Defaults to the configured
|
|
44
|
+
* `chatModelId`.
|
|
45
|
+
*
|
|
46
|
+
* The subagent path passes `ValidatedRecipe.primaryModelId`, which is the
|
|
47
|
+
* host's own configured id — `validateRecipe` copies the pair on, and a recipe
|
|
48
|
+
* has no field to name a model with. So this parameterizes the pair without
|
|
49
|
+
* ever widening which models are reachable.
|
|
50
|
+
*/
|
|
51
|
+
primaryModelId?: string;
|
|
52
|
+
/** The provider's model id for the fallback slot. See {@link primaryModelId}. */
|
|
53
|
+
fallbackModelId?: string;
|
|
54
|
+
/** AI Gateway log metadata for correlation — see {@link AiGatewayMetadata}. */
|
|
55
|
+
metadata?: AiGatewayMetadata;
|
|
56
|
+
}
|
|
57
|
+
/** The primary/fallback models (lazily memoized) plus their ids for logging. */
|
|
58
|
+
export interface ModelPair {
|
|
59
|
+
primary: () => LanguageModel;
|
|
60
|
+
fallback: () => LanguageModel;
|
|
61
|
+
primaryId: () => string;
|
|
62
|
+
fallbackId: () => string;
|
|
63
|
+
}
|
|
64
|
+
export interface ModelRuntime {
|
|
65
|
+
/**
|
|
66
|
+
* Lazily build + memoize a primary/fallback model pair (overridable in tests,
|
|
67
|
+
* id-parameterized so a subagent can run the pair its validated recipe
|
|
68
|
+
* carries). Nothing is checked here — the ids reaching this can only be the
|
|
69
|
+
* host's own, which `resolveConfig` has already proven non-empty and distinct.
|
|
70
|
+
*/
|
|
71
|
+
createModelPair(overrides?: ModelOverrides): ModelPair;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* How a provider is supplied to an agent: given the Worker env and the agent's
|
|
75
|
+
* *resolved* model config, return a runtime.
|
|
76
|
+
*
|
|
77
|
+
* Both base-class seams — `DynamicAgent.modelRuntime` and
|
|
78
|
+
* `RecipeSubagentHost.modelRuntime` — take this shape, which is the point of it.
|
|
79
|
+
* A provider written as one of these is defined once and referenced from the
|
|
80
|
+
* agent and its subagent facet, instead of being spelled out twice in two class
|
|
81
|
+
* bodies that nothing keeps in step. See
|
|
82
|
+
* {@link file://./workers-ai/runtime.ts workersAIModels} for core's own.
|
|
83
|
+
*
|
|
84
|
+
* Config arrives as an argument rather than being read off `this`: the facet
|
|
85
|
+
* resolves its config inside `buildRuntime` and calls the seam from there, so
|
|
86
|
+
* there is no `this.config` to read at that point — and a factory that cannot
|
|
87
|
+
* reach for one cannot disagree with its caller about which AI Gateway the agent
|
|
88
|
+
* is on.
|
|
89
|
+
*/
|
|
90
|
+
export type ModelRuntimeFactory<TEnv> = (env: TEnv, config: ModelConfig) => ModelRuntime;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { LanguageModel, ToolSet } from "ai";
|
|
2
|
+
import { Session } from "agents/experimental/memory/session";
|
|
3
|
+
import type { SessionMessage } from "agents/experimental/memory/session";
|
|
4
|
+
import { createCompactFunction } from "agents/experimental/memory/utils";
|
|
5
|
+
/**
|
|
6
|
+
* The one continuous {@link Session} an agent Durable Object owns: soul + memory
|
|
7
|
+
* + compaction, one Session per DO.
|
|
8
|
+
*
|
|
9
|
+
* Compaction is the one **lossy** thing this module does, so it is also the one
|
|
10
|
+
* thing it announces: `onMessagesDisplaced` hands over the raw messages a
|
|
11
|
+
* summary is about to replace. Core neither stores them nor knows who wants
|
|
12
|
+
* them — a host wires the seam to whatever does.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The SQLite-backed host the Sessions API needs — satisfied by the Agents SDK
|
|
16
|
+
* `Agent` (`this.sql`).
|
|
17
|
+
*/
|
|
18
|
+
export interface SessionHost {
|
|
19
|
+
sql<T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]): T[];
|
|
20
|
+
}
|
|
21
|
+
/** The subset of `Session` the agent loop drives — lets tests inject a fake. */
|
|
22
|
+
export interface SessionLike {
|
|
23
|
+
appendMessage(message: SessionMessage, parentId?: string | null): Promise<unknown> | unknown;
|
|
24
|
+
getHistory(): Promise<SessionMessage[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Read one message by id, or null. Reads the **raw stored row**, so it is
|
|
27
|
+
* unaffected by compaction overlays — a message folded into a summary is still
|
|
28
|
+
* readable here. That is what makes {@link appendOnce}'s read-back a reliable
|
|
29
|
+
* recovery path for a round whose Workflow step re-ran.
|
|
30
|
+
*/
|
|
31
|
+
getMessage(id: string): Promise<SessionMessage | null>;
|
|
32
|
+
refreshSystemPrompt(): Promise<string>;
|
|
33
|
+
tools(): Promise<ToolSet>;
|
|
34
|
+
/** Compaction overlays so far — non-empty ⇒ history has been displaced. */
|
|
35
|
+
getCompactions(): Promise<unknown[]>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Append a message with a deterministic id exactly once, and return the text that
|
|
39
|
+
* is **durably stored** under that id.
|
|
40
|
+
*
|
|
41
|
+
* `Session.appendMessage` is already idempotent by id: appending an id that
|
|
42
|
+
* exists is a no-op. The read-back is what matters for a re-run step — if it
|
|
43
|
+
* crashed after appending and the retry re-inferred a *different* reply, the
|
|
44
|
+
* append no-ops and this returns the original, durable text. The Session and the
|
|
45
|
+
* value the caller goes on to deliver therefore never disagree.
|
|
46
|
+
*
|
|
47
|
+
* Falls back to the message's own text if the read-back returns null (it cannot,
|
|
48
|
+
* having just been appended) rather than failing a round over a missing echo.
|
|
49
|
+
*/
|
|
50
|
+
export declare function appendOnce(session: SessionLike, message: SessionMessage): Promise<string>;
|
|
51
|
+
export interface AgentSessionOptions {
|
|
52
|
+
/** Read-only identity block injected into the system prompt every turn. */
|
|
53
|
+
soul: () => string | Promise<string>;
|
|
54
|
+
/** Description of the writable SQLite `"memory"` scratchpad the model self-edits. */
|
|
55
|
+
memoryDescription: string;
|
|
56
|
+
/** Soft cap (tokens) for the `"memory"` block. */
|
|
57
|
+
memoryMaxTokens: number;
|
|
58
|
+
/** History token threshold that triggers compaction. */
|
|
59
|
+
compactAfterTokens: number;
|
|
60
|
+
/**
|
|
61
|
+
* Tokens of recent history compaction keeps verbatim. Coupled to
|
|
62
|
+
* {@link compactAfterTokens} — see `SessionConfig.compactTailTokens` for the
|
|
63
|
+
* invariant that binds them, which `resolveConfig` enforces.
|
|
64
|
+
*/
|
|
65
|
+
compactTailTokens: number;
|
|
66
|
+
/**
|
|
67
|
+
* Output-token ceiling for the summarizer call. An unbounded summary is not
|
|
68
|
+
* the risk; a silently truncated one is — it becomes this caller's memory of
|
|
69
|
+
* everything that scrolled out, with no way to tell it was cut short.
|
|
70
|
+
*/
|
|
71
|
+
maxOutputTokens: number;
|
|
72
|
+
/**
|
|
73
|
+
* Hand over the raw messages each compaction displaces, before a summary
|
|
74
|
+
* replaces them. Best-effort: a throw here must never abort compaction.
|
|
75
|
+
*
|
|
76
|
+
* The seam, not a policy — pass `runtime.onMessagesDisplaced` to reach every
|
|
77
|
+
* installed plugin declaring the hook, or any function of your own.
|
|
78
|
+
*/
|
|
79
|
+
onMessagesDisplaced?: (messages: SessionMessage[]) => Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
type CompactFn = ReturnType<typeof createCompactFunction>;
|
|
82
|
+
/**
|
|
83
|
+
* Wrap a compaction function so the raw messages it folds into a summary are
|
|
84
|
+
* also handed to `onMessagesDisplaced` before they stop being readable as
|
|
85
|
+
* history. The displaced range is `fromMessageId..toMessageId` of the result,
|
|
86
|
+
* sliced from the `history` the compaction saw.
|
|
87
|
+
*
|
|
88
|
+
* A listener's failure is swallowed — compaction must still shorten history
|
|
89
|
+
* when whatever is listening is briefly unavailable. The alternative is
|
|
90
|
+
* unbounded context because a side concern is down.
|
|
91
|
+
*/
|
|
92
|
+
export declare function notifyingCompaction(base: CompactFn, onMessagesDisplaced?: (messages: SessionMessage[]) => Promise<void>): CompactFn;
|
|
93
|
+
/**
|
|
94
|
+
* Build the one continuous `Session` an agent Durable Object owns: a read-only
|
|
95
|
+
* `"soul"` identity block + a writable `"memory"` scratchpad, with history
|
|
96
|
+
* compaction summarized by the same model. All of a caller's turns (any channel
|
|
97
|
+
* or thread) accumulate into this single conversation.
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildAgentSession(agent: SessionHost, model: LanguageModel, opts: AgentSessionOptions): Session;
|
|
100
|
+
export {};
|