@lunora/agent 0.0.0 → 1.0.0-alpha.1
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.md +105 -0
- package/README.md +48 -43
- package/dist/channels.d.mts +54 -0
- package/dist/channels.d.ts +54 -0
- package/dist/channels.mjs +181 -0
- package/dist/component.d.mts +101 -0
- package/dist/component.d.ts +101 -0
- package/dist/component.mjs +407 -0
- package/dist/inbound.d.mts +29 -0
- package/dist/inbound.d.ts +29 -0
- package/dist/inbound.mjs +32 -0
- package/dist/index.d.mts +740 -0
- package/dist/index.d.ts +740 -0
- package/dist/index.mjs +18 -0
- package/dist/naming.d.mts +17 -0
- package/dist/naming.d.ts +17 -0
- package/dist/naming.mjs +8 -0
- package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +24 -0
- package/dist/packem_shared/VoiceSessionDO-DLoXsHGF.mjs +297 -0
- package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +65 -0
- package/dist/packem_shared/agentAsTool-Dt8NlU6k.mjs +94 -0
- package/dist/packem_shared/base64-BVwtgRJV.mjs +18 -0
- package/dist/packem_shared/braintrustTelemetry-wuGDErob.mjs +47 -0
- package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +69 -0
- package/dist/packem_shared/codeTool-CjgJOC9t.mjs +122 -0
- package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +97 -0
- package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +43 -0
- package/dist/packem_shared/common-DAeFCot5.mjs +61 -0
- package/dist/packem_shared/compileAgentWorkflow-BxJjHgtD.mjs +55 -0
- package/dist/packem_shared/consoleTelemetry-z2MiP1jt.mjs +93 -0
- package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +50 -0
- package/dist/packem_shared/createAgentGenerate-BQv9YJ01.mjs +192 -0
- package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +69 -0
- package/dist/packem_shared/defineAgent-D6maSbVc.mjs +148 -0
- package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +22 -0
- package/dist/packem_shared/functionTool-D6lCa2jB.mjs +20 -0
- package/dist/packem_shared/graph-component-aoUwO-f0.mjs +216 -0
- package/dist/packem_shared/memory-D4FPcBsX.mjs +12 -0
- package/dist/packem_shared/normalizeEntityName-CyEEWFkR.mjs +3 -0
- package/dist/packem_shared/runAgentLoop-Dhg4ZNvw.mjs +493 -0
- package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +211 -0
- package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +194 -0
- package/dist/packem_shared/sentryTelemetry-CgqFJyLO.mjs +36 -0
- package/dist/packem_shared/types.d-BWG0uUtX.d.mts +1015 -0
- package/dist/packem_shared/types.d-BWG0uUtX.d.ts +1015 -0
- package/dist/sandbox.d.mts +185 -0
- package/dist/sandbox.d.ts +185 -0
- package/dist/sandbox.mjs +109 -0
- package/dist/telemetry/index.d.mts +150 -0
- package/dist/telemetry/index.d.ts +150 -0
- package/dist/telemetry/index.mjs +4 -0
- package/package.json +88 -7
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { a as AgentToolContext, b as AgentToolDefinition } from "./packem_shared/types.d-BWG0uUtX.mjs";
|
|
2
|
+
import '@lunora/mail/inbound';
|
|
3
|
+
import 'ai';
|
|
4
|
+
/**
|
|
5
|
+
* The model-provided input to a {@link browserTool} call — a discriminated
|
|
6
|
+
* union on `op` so one tool exposes every headless-browser capability.
|
|
7
|
+
*/
|
|
8
|
+
type BrowserToolInput = {
|
|
9
|
+
fullPage?: boolean;
|
|
10
|
+
op: "screenshot";
|
|
11
|
+
type?: "jpeg" | "png";
|
|
12
|
+
url: string;
|
|
13
|
+
} | {
|
|
14
|
+
op: "content";
|
|
15
|
+
url: string;
|
|
16
|
+
} | {
|
|
17
|
+
op: "pdf";
|
|
18
|
+
url: string;
|
|
19
|
+
} | {
|
|
20
|
+
op: "scrape";
|
|
21
|
+
selector?: string;
|
|
22
|
+
url: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The model-provided input to a {@link containerTool} call — a discriminated
|
|
26
|
+
* union on `op`. `fetch` sends an HTTP request to the container; `exec` asks it
|
|
27
|
+
* to run a command (routed as a POST to `/exec`, since the container surface
|
|
28
|
+
* exposes no first-class exec RPC — the container app must serve that route).
|
|
29
|
+
*/
|
|
30
|
+
type ContainerToolInput = {
|
|
31
|
+
args?: string[];
|
|
32
|
+
command?: string;
|
|
33
|
+
op: "exec";
|
|
34
|
+
} | {
|
|
35
|
+
body?: string;
|
|
36
|
+
method?: string;
|
|
37
|
+
op: "fetch";
|
|
38
|
+
path: string;
|
|
39
|
+
};
|
|
40
|
+
/** Author-supplied config for `browserTool`. */
|
|
41
|
+
interface BrowserToolOptions {
|
|
42
|
+
/** Override the model-facing description (what the tool does). */
|
|
43
|
+
description?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Gate a call behind a human approval. Defaults to **unattended** (no gate),
|
|
46
|
+
* matching the AI SDK. `browserTool` fetches a model-chosen `url` with no
|
|
47
|
+
* allowlist, so it is an SSRF surface: a prompt-injected model can point it at
|
|
48
|
+
* an internal/link-local address and read the response back into context. Pass
|
|
49
|
+
* a boolean or a predicate to gate it (evaluated from replay-stable input, so
|
|
50
|
+
* keep it deterministic).
|
|
51
|
+
*/
|
|
52
|
+
needsApproval?: ((input: BrowserToolInput) => boolean) | boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The model-provided input to a {@link fsTool} call — a discriminated union on
|
|
56
|
+
* `op` over an R2-backed virtual filesystem. Paths are relative to the tool's
|
|
57
|
+
* pinned `root`; a `..` that escapes the root is rejected server-side.
|
|
58
|
+
*/
|
|
59
|
+
type FsToolInput = {
|
|
60
|
+
op: "ls";
|
|
61
|
+
path?: string;
|
|
62
|
+
} | {
|
|
63
|
+
op: "read";
|
|
64
|
+
path: string;
|
|
65
|
+
} | {
|
|
66
|
+
op: "rm";
|
|
67
|
+
path: string;
|
|
68
|
+
} | {
|
|
69
|
+
op: "stat";
|
|
70
|
+
path: string;
|
|
71
|
+
} | {
|
|
72
|
+
content: string;
|
|
73
|
+
op: "write";
|
|
74
|
+
path: string;
|
|
75
|
+
};
|
|
76
|
+
/** Author-supplied config for `fsTool`. */
|
|
77
|
+
interface FsToolOptions {
|
|
78
|
+
/** Override the model-facing description (what the tool does). */
|
|
79
|
+
description?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Gate a call behind a human approval. Defaults to gating the WRITING ops
|
|
82
|
+
* (`write`, `rm`) — a prompt-injected model shouldn't silently overwrite or
|
|
83
|
+
* delete the sandbox — while `ls`/`read`/`stat` run unattended. Pass a boolean
|
|
84
|
+
* or a predicate (evaluated from replay-stable input, so keep it deterministic).
|
|
85
|
+
*/
|
|
86
|
+
needsApproval?: ((input: FsToolInput) => boolean) | boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The key prefix every path is scoped under, isolating this tool's files from
|
|
89
|
+
* the rest of the bucket. Either a fixed string (e.g. `"agents/support"`) or a
|
|
90
|
+
* function of the tool context — return `` `agents/${ctx.threadKey}` `` (or an
|
|
91
|
+
* owner-derived prefix) to give each run/user its OWN namespace in a
|
|
92
|
+
* multi-tenant agent. Default: the bucket root (shared — set a root for
|
|
93
|
+
* multi-tenant use). The server rejects any `..` that would escape it.
|
|
94
|
+
*/
|
|
95
|
+
root?: ((context: AgentToolContext) => string) | string;
|
|
96
|
+
}
|
|
97
|
+
/** Author-supplied config for `containerTool`. */
|
|
98
|
+
interface ContainerToolOptions {
|
|
99
|
+
/** Override the model-facing description (what the tool does). */
|
|
100
|
+
description?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Gate a call behind a human approval. Defaults to gating any command
|
|
103
|
+
* execution: an `exec`, AND a `fetch` whose path resolves to the privileged
|
|
104
|
+
* `/exec` route (both reach the same command-execution path in the container,
|
|
105
|
+
* so gating on the `op` name alone would let a `fetch` to `/exec` run a
|
|
106
|
+
* command unattended). A plain `fetch` to any other route runs unattended.
|
|
107
|
+
* Pass a boolean or your own predicate to change that. Evaluated from
|
|
108
|
+
* replay-stable input, so keep it deterministic.
|
|
109
|
+
*/
|
|
110
|
+
needsApproval?: ((input: ContainerToolInput) => boolean) | boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A batteries-included agent tool that drives Cloudflare Browser Rendering. One
|
|
114
|
+
* tool exposes every browser op (screenshot / pdf / content / scrape); the model
|
|
115
|
+
* picks via `op`. The call dispatches to the auto-registered `sandbox:invoke`
|
|
116
|
+
* action — which runs on an action ctx carrying `ctx.browser`. Importing the tool
|
|
117
|
+
* makes codegen register the dispatcher and provision the `BROWSER` binding; like
|
|
118
|
+
* any `ctx.browser` user, the app still supplies a `config.browser` thunk
|
|
119
|
+
* (`createBrowser({ binding: env.BROWSER, launch })` with the optional
|
|
120
|
+
* `@cloudflare/playwright` `launch` peer) to `createShardDO()` — codegen never
|
|
121
|
+
* injects that peer, so the browser op throws a directed error until it is wired.
|
|
122
|
+
*
|
|
123
|
+
* Security: the model chooses the `url` with no allowlist, so this is an SSRF
|
|
124
|
+
* surface — pass `opts.needsApproval` to gate calls a prompt-injected model
|
|
125
|
+
* could aim at internal/link-local endpoints.
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { browserTool, defineAgent } from "@lunora/agent/sandbox";
|
|
129
|
+
*
|
|
130
|
+
* export const researcher = defineAgent({
|
|
131
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
132
|
+
* tools: { browser: browserTool() },
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
declare const browserTool: (options?: BrowserToolOptions) => AgentToolDefinition<BrowserToolInput, string>;
|
|
137
|
+
/**
|
|
138
|
+
* A batteries-included agent tool that talks to a declared Cloudflare
|
|
139
|
+
* Container. `name` is the `ctx.containers.<name>` key (the `lunora/containers.ts`
|
|
140
|
+
* export). One tool exposes `fetch` (HTTP request) and `exec` (run a command);
|
|
141
|
+
* the model picks via `op`. The call dispatches to the auto-registered
|
|
142
|
+
* `sandbox:invoke` action, which carries `ctx.containers`.
|
|
143
|
+
*
|
|
144
|
+
* By default a `fetch` runs unattended while command execution is gated behind a
|
|
145
|
+
* human approval — an `exec`, AND a `fetch` whose path resolves to the privileged
|
|
146
|
+
* `/exec` route (both reach the same command-execution path in the container, so
|
|
147
|
+
* gating on the `op` name alone would let a `fetch` to `/exec` run a command
|
|
148
|
+
* unattended). Note a `fetch` can still reach any *other* container route
|
|
149
|
+
* unattended, so scope the container's routes accordingly. Pass
|
|
150
|
+
* `opts.needsApproval` to widen or disable the gate.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { containerTool, defineAgent } from "@lunora/agent/sandbox";
|
|
154
|
+
*
|
|
155
|
+
* export const ops = defineAgent({
|
|
156
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
157
|
+
* tools: { sandbox: containerTool("sandbox") },
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare const containerTool: (name: string, options?: ContainerToolOptions) => AgentToolDefinition<ContainerToolInput, string>;
|
|
162
|
+
/**
|
|
163
|
+
* A batteries-included agent tool exposing a persistent, R2-backed virtual
|
|
164
|
+
* filesystem. `bucket` is the R2 binding name; every path is scoped under the
|
|
165
|
+
* pinned `opts.root` (a `..` that would escape it is rejected). One tool exposes
|
|
166
|
+
* `ls`/`read`/`write`/`rm`/`stat`; the model picks via `op`. The call dispatches
|
|
167
|
+
* to the auto-registered `sandbox:invoke` action, which reads the bucket from
|
|
168
|
+
* `ctx.env`. workerd has no real shell — this is object-store-backed file I/O.
|
|
169
|
+
*
|
|
170
|
+
* By default the writing ops (`write`/`rm`) pause for a human approval while
|
|
171
|
+
* reads run unattended; pass `opts.needsApproval` to change that. Importing the
|
|
172
|
+
* tool registers the dispatcher; the app must declare the `r2_bucket` binding in
|
|
173
|
+
* `wrangler.jsonc` (the fs op throws a directed error until it is wired).
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { defineAgent, fsTool } from "@lunora/agent/sandbox";
|
|
177
|
+
*
|
|
178
|
+
* export const coder = defineAgent({
|
|
179
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
180
|
+
* tools: { fs: fsTool("SANDBOX_BUCKET", { root: "agents/coder" }) },
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare const fsTool: (bucket: string, options?: FsToolOptions) => AgentToolDefinition<FsToolInput>;
|
|
185
|
+
export { type BrowserToolInput, type BrowserToolOptions, type ContainerToolInput, type ContainerToolOptions, type FsToolInput, type FsToolOptions, browserTool, containerTool, fsTool };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { a as AgentToolContext, b as AgentToolDefinition } from "./packem_shared/types.d-BWG0uUtX.js";
|
|
2
|
+
import '@lunora/mail/inbound';
|
|
3
|
+
import 'ai';
|
|
4
|
+
/**
|
|
5
|
+
* The model-provided input to a {@link browserTool} call — a discriminated
|
|
6
|
+
* union on `op` so one tool exposes every headless-browser capability.
|
|
7
|
+
*/
|
|
8
|
+
type BrowserToolInput = {
|
|
9
|
+
fullPage?: boolean;
|
|
10
|
+
op: "screenshot";
|
|
11
|
+
type?: "jpeg" | "png";
|
|
12
|
+
url: string;
|
|
13
|
+
} | {
|
|
14
|
+
op: "content";
|
|
15
|
+
url: string;
|
|
16
|
+
} | {
|
|
17
|
+
op: "pdf";
|
|
18
|
+
url: string;
|
|
19
|
+
} | {
|
|
20
|
+
op: "scrape";
|
|
21
|
+
selector?: string;
|
|
22
|
+
url: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The model-provided input to a {@link containerTool} call — a discriminated
|
|
26
|
+
* union on `op`. `fetch` sends an HTTP request to the container; `exec` asks it
|
|
27
|
+
* to run a command (routed as a POST to `/exec`, since the container surface
|
|
28
|
+
* exposes no first-class exec RPC — the container app must serve that route).
|
|
29
|
+
*/
|
|
30
|
+
type ContainerToolInput = {
|
|
31
|
+
args?: string[];
|
|
32
|
+
command?: string;
|
|
33
|
+
op: "exec";
|
|
34
|
+
} | {
|
|
35
|
+
body?: string;
|
|
36
|
+
method?: string;
|
|
37
|
+
op: "fetch";
|
|
38
|
+
path: string;
|
|
39
|
+
};
|
|
40
|
+
/** Author-supplied config for `browserTool`. */
|
|
41
|
+
interface BrowserToolOptions {
|
|
42
|
+
/** Override the model-facing description (what the tool does). */
|
|
43
|
+
description?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Gate a call behind a human approval. Defaults to **unattended** (no gate),
|
|
46
|
+
* matching the AI SDK. `browserTool` fetches a model-chosen `url` with no
|
|
47
|
+
* allowlist, so it is an SSRF surface: a prompt-injected model can point it at
|
|
48
|
+
* an internal/link-local address and read the response back into context. Pass
|
|
49
|
+
* a boolean or a predicate to gate it (evaluated from replay-stable input, so
|
|
50
|
+
* keep it deterministic).
|
|
51
|
+
*/
|
|
52
|
+
needsApproval?: ((input: BrowserToolInput) => boolean) | boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The model-provided input to a {@link fsTool} call — a discriminated union on
|
|
56
|
+
* `op` over an R2-backed virtual filesystem. Paths are relative to the tool's
|
|
57
|
+
* pinned `root`; a `..` that escapes the root is rejected server-side.
|
|
58
|
+
*/
|
|
59
|
+
type FsToolInput = {
|
|
60
|
+
op: "ls";
|
|
61
|
+
path?: string;
|
|
62
|
+
} | {
|
|
63
|
+
op: "read";
|
|
64
|
+
path: string;
|
|
65
|
+
} | {
|
|
66
|
+
op: "rm";
|
|
67
|
+
path: string;
|
|
68
|
+
} | {
|
|
69
|
+
op: "stat";
|
|
70
|
+
path: string;
|
|
71
|
+
} | {
|
|
72
|
+
content: string;
|
|
73
|
+
op: "write";
|
|
74
|
+
path: string;
|
|
75
|
+
};
|
|
76
|
+
/** Author-supplied config for `fsTool`. */
|
|
77
|
+
interface FsToolOptions {
|
|
78
|
+
/** Override the model-facing description (what the tool does). */
|
|
79
|
+
description?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Gate a call behind a human approval. Defaults to gating the WRITING ops
|
|
82
|
+
* (`write`, `rm`) — a prompt-injected model shouldn't silently overwrite or
|
|
83
|
+
* delete the sandbox — while `ls`/`read`/`stat` run unattended. Pass a boolean
|
|
84
|
+
* or a predicate (evaluated from replay-stable input, so keep it deterministic).
|
|
85
|
+
*/
|
|
86
|
+
needsApproval?: ((input: FsToolInput) => boolean) | boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The key prefix every path is scoped under, isolating this tool's files from
|
|
89
|
+
* the rest of the bucket. Either a fixed string (e.g. `"agents/support"`) or a
|
|
90
|
+
* function of the tool context — return `` `agents/${ctx.threadKey}` `` (or an
|
|
91
|
+
* owner-derived prefix) to give each run/user its OWN namespace in a
|
|
92
|
+
* multi-tenant agent. Default: the bucket root (shared — set a root for
|
|
93
|
+
* multi-tenant use). The server rejects any `..` that would escape it.
|
|
94
|
+
*/
|
|
95
|
+
root?: ((context: AgentToolContext) => string) | string;
|
|
96
|
+
}
|
|
97
|
+
/** Author-supplied config for `containerTool`. */
|
|
98
|
+
interface ContainerToolOptions {
|
|
99
|
+
/** Override the model-facing description (what the tool does). */
|
|
100
|
+
description?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Gate a call behind a human approval. Defaults to gating any command
|
|
103
|
+
* execution: an `exec`, AND a `fetch` whose path resolves to the privileged
|
|
104
|
+
* `/exec` route (both reach the same command-execution path in the container,
|
|
105
|
+
* so gating on the `op` name alone would let a `fetch` to `/exec` run a
|
|
106
|
+
* command unattended). A plain `fetch` to any other route runs unattended.
|
|
107
|
+
* Pass a boolean or your own predicate to change that. Evaluated from
|
|
108
|
+
* replay-stable input, so keep it deterministic.
|
|
109
|
+
*/
|
|
110
|
+
needsApproval?: ((input: ContainerToolInput) => boolean) | boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A batteries-included agent tool that drives Cloudflare Browser Rendering. One
|
|
114
|
+
* tool exposes every browser op (screenshot / pdf / content / scrape); the model
|
|
115
|
+
* picks via `op`. The call dispatches to the auto-registered `sandbox:invoke`
|
|
116
|
+
* action — which runs on an action ctx carrying `ctx.browser`. Importing the tool
|
|
117
|
+
* makes codegen register the dispatcher and provision the `BROWSER` binding; like
|
|
118
|
+
* any `ctx.browser` user, the app still supplies a `config.browser` thunk
|
|
119
|
+
* (`createBrowser({ binding: env.BROWSER, launch })` with the optional
|
|
120
|
+
* `@cloudflare/playwright` `launch` peer) to `createShardDO()` — codegen never
|
|
121
|
+
* injects that peer, so the browser op throws a directed error until it is wired.
|
|
122
|
+
*
|
|
123
|
+
* Security: the model chooses the `url` with no allowlist, so this is an SSRF
|
|
124
|
+
* surface — pass `opts.needsApproval` to gate calls a prompt-injected model
|
|
125
|
+
* could aim at internal/link-local endpoints.
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { browserTool, defineAgent } from "@lunora/agent/sandbox";
|
|
129
|
+
*
|
|
130
|
+
* export const researcher = defineAgent({
|
|
131
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
132
|
+
* tools: { browser: browserTool() },
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
declare const browserTool: (options?: BrowserToolOptions) => AgentToolDefinition<BrowserToolInput, string>;
|
|
137
|
+
/**
|
|
138
|
+
* A batteries-included agent tool that talks to a declared Cloudflare
|
|
139
|
+
* Container. `name` is the `ctx.containers.<name>` key (the `lunora/containers.ts`
|
|
140
|
+
* export). One tool exposes `fetch` (HTTP request) and `exec` (run a command);
|
|
141
|
+
* the model picks via `op`. The call dispatches to the auto-registered
|
|
142
|
+
* `sandbox:invoke` action, which carries `ctx.containers`.
|
|
143
|
+
*
|
|
144
|
+
* By default a `fetch` runs unattended while command execution is gated behind a
|
|
145
|
+
* human approval — an `exec`, AND a `fetch` whose path resolves to the privileged
|
|
146
|
+
* `/exec` route (both reach the same command-execution path in the container, so
|
|
147
|
+
* gating on the `op` name alone would let a `fetch` to `/exec` run a command
|
|
148
|
+
* unattended). Note a `fetch` can still reach any *other* container route
|
|
149
|
+
* unattended, so scope the container's routes accordingly. Pass
|
|
150
|
+
* `opts.needsApproval` to widen or disable the gate.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { containerTool, defineAgent } from "@lunora/agent/sandbox";
|
|
154
|
+
*
|
|
155
|
+
* export const ops = defineAgent({
|
|
156
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
157
|
+
* tools: { sandbox: containerTool("sandbox") },
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare const containerTool: (name: string, options?: ContainerToolOptions) => AgentToolDefinition<ContainerToolInput, string>;
|
|
162
|
+
/**
|
|
163
|
+
* A batteries-included agent tool exposing a persistent, R2-backed virtual
|
|
164
|
+
* filesystem. `bucket` is the R2 binding name; every path is scoped under the
|
|
165
|
+
* pinned `opts.root` (a `..` that would escape it is rejected). One tool exposes
|
|
166
|
+
* `ls`/`read`/`write`/`rm`/`stat`; the model picks via `op`. The call dispatches
|
|
167
|
+
* to the auto-registered `sandbox:invoke` action, which reads the bucket from
|
|
168
|
+
* `ctx.env`. workerd has no real shell — this is object-store-backed file I/O.
|
|
169
|
+
*
|
|
170
|
+
* By default the writing ops (`write`/`rm`) pause for a human approval while
|
|
171
|
+
* reads run unattended; pass `opts.needsApproval` to change that. Importing the
|
|
172
|
+
* tool registers the dispatcher; the app must declare the `r2_bucket` binding in
|
|
173
|
+
* `wrangler.jsonc` (the fs op throws a directed error until it is wired).
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { defineAgent, fsTool } from "@lunora/agent/sandbox";
|
|
177
|
+
*
|
|
178
|
+
* export const coder = defineAgent({
|
|
179
|
+
* model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
180
|
+
* tools: { fs: fsTool("SANDBOX_BUCKET", { root: "agents/coder" }) },
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare const fsTool: (bucket: string, options?: FsToolOptions) => AgentToolDefinition<FsToolInput>;
|
|
185
|
+
export { type BrowserToolInput, type BrowserToolOptions, type ContainerToolInput, type ContainerToolOptions, type FsToolInput, type FsToolOptions, browserTool, containerTool, fsTool };
|
package/dist/sandbox.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { jsonSchema } from 'ai';
|
|
3
|
+
import { toFunctionReference, SANDBOX_INVOKE_PATH } from './packem_shared/AGENT_MODULE-Dnt_-AAT.mjs';
|
|
4
|
+
|
|
5
|
+
const SANDBOX_REF = toFunctionReference(SANDBOX_INVOKE_PATH);
|
|
6
|
+
const DEFAULT_BROWSER_DESCRIPTION = 'Drive a headless browser: screenshot a page, render it to PDF, read its HTML content, or scrape it. Set `op` to "screenshot" | "pdf" | "content" | "scrape" and pass the target `url`.';
|
|
7
|
+
const DEFAULT_CONTAINER_DESCRIPTION = 'Talk to a sandboxed container: `fetch` an HTTP path on it, or `exec` a command inside it. Set `op` to "fetch" (with `path`) or "exec" (with `command`).';
|
|
8
|
+
const DEFAULT_FS_DESCRIPTION = 'Read and write files in a persistent sandbox filesystem. Set `op` to "ls" (list a directory), "read", "write" (with `content`), "rm", or "stat", and pass `path`.';
|
|
9
|
+
const defaultFsGate = (input) => input.op === "write" || input.op === "rm";
|
|
10
|
+
const FS_TOOL_SCHEMA = jsonSchema({
|
|
11
|
+
properties: {
|
|
12
|
+
content: { description: "write: the file contents.", type: "string" },
|
|
13
|
+
op: { description: "The filesystem operation to run.", enum: ["ls", "read", "write", "rm", "stat"], type: "string" },
|
|
14
|
+
path: { description: "The file or directory path, relative to the sandbox root.", type: "string" }
|
|
15
|
+
},
|
|
16
|
+
required: ["op"],
|
|
17
|
+
type: "object"
|
|
18
|
+
});
|
|
19
|
+
const BROWSER_TOOL_SCHEMA = jsonSchema({
|
|
20
|
+
properties: {
|
|
21
|
+
fullPage: { description: "screenshot: capture the full scrollable page rather than just the viewport.", type: "boolean" },
|
|
22
|
+
op: { description: "The browser operation to run.", enum: ["screenshot", "scrape", "pdf", "content"], type: "string" },
|
|
23
|
+
selector: { description: "scrape: optional CSS selector hint to narrow the extraction.", type: "string" },
|
|
24
|
+
type: { description: "screenshot: image encoding (default png).", enum: ["png", "jpeg"], type: "string" },
|
|
25
|
+
url: { description: "The URL to operate on.", type: "string" }
|
|
26
|
+
},
|
|
27
|
+
required: ["op", "url"],
|
|
28
|
+
type: "object"
|
|
29
|
+
});
|
|
30
|
+
const CONTAINER_EXEC_ROUTE = "/exec";
|
|
31
|
+
const CONTAINER_PATH_QUERY_SPLIT = /[?#]/u;
|
|
32
|
+
const normalizeContainerPath = (path) => {
|
|
33
|
+
const [raw = ""] = (path ?? "").split(CONTAINER_PATH_QUERY_SPLIT);
|
|
34
|
+
const segments = [];
|
|
35
|
+
for (const segment of raw.split("/")) {
|
|
36
|
+
if (segment === "" || segment === ".") {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (segment === "..") {
|
|
40
|
+
segments.pop();
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
segments.push(segment);
|
|
44
|
+
}
|
|
45
|
+
return `/${segments.join("/")}`;
|
|
46
|
+
};
|
|
47
|
+
const defaultContainerGate = (input) => {
|
|
48
|
+
if (input.op === "exec") {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return normalizeContainerPath(input.path) === CONTAINER_EXEC_ROUTE;
|
|
52
|
+
};
|
|
53
|
+
const CONTAINER_TOOL_SCHEMA = jsonSchema({
|
|
54
|
+
properties: {
|
|
55
|
+
args: { description: "exec: command arguments.", items: { type: "string" }, type: "array" },
|
|
56
|
+
body: { description: "fetch: request body.", type: "string" },
|
|
57
|
+
command: { description: "exec: the command to run.", type: "string" },
|
|
58
|
+
method: { description: "fetch: HTTP method (default GET).", type: "string" },
|
|
59
|
+
op: { description: "The container operation to run.", enum: ["fetch", "exec"], type: "string" },
|
|
60
|
+
path: { description: "fetch: request path on the container (e.g. /health).", type: "string" }
|
|
61
|
+
},
|
|
62
|
+
required: ["op"],
|
|
63
|
+
type: "object"
|
|
64
|
+
});
|
|
65
|
+
const browserTool = (options = {}) => {
|
|
66
|
+
return {
|
|
67
|
+
description: options.description ?? DEFAULT_BROWSER_DESCRIPTION,
|
|
68
|
+
// Pin `kind` LAST so out-of-schema model input can never override it.
|
|
69
|
+
execute: (input, context) => context.run(SANDBOX_REF, { ...input, kind: "browser" }),
|
|
70
|
+
inputSchema: BROWSER_TOOL_SCHEMA,
|
|
71
|
+
isLunoraAgentTool: true,
|
|
72
|
+
...options.needsApproval === void 0 ? {} : { needsApproval: options.needsApproval }
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const containerTool = (name, options = {}) => {
|
|
76
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
77
|
+
throw new LunoraError("INTERNAL", "@lunora/agent: containerTool requires a container `name` (the ctx.containers.<name> key from lunora/containers.ts)");
|
|
78
|
+
}
|
|
79
|
+
const needsApproval = options.needsApproval ?? defaultContainerGate;
|
|
80
|
+
return {
|
|
81
|
+
description: options.description ?? DEFAULT_CONTAINER_DESCRIPTION,
|
|
82
|
+
// Pin `kind`/`name` LAST so out-of-schema model input can never override
|
|
83
|
+
// the authoritative container the author pinned.
|
|
84
|
+
execute: (input, context) => context.run(SANDBOX_REF, { ...input, kind: "container", name }),
|
|
85
|
+
inputSchema: CONTAINER_TOOL_SCHEMA,
|
|
86
|
+
isLunoraAgentTool: true,
|
|
87
|
+
needsApproval
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const fsTool = (bucket, options = {}) => {
|
|
91
|
+
if (typeof bucket !== "string" || bucket.length === 0) {
|
|
92
|
+
throw new LunoraError("INTERNAL", "@lunora/agent: fsTool requires an R2 `bucket` binding name (the r2_bucket declared in wrangler.jsonc)");
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
description: options.description ?? DEFAULT_FS_DESCRIPTION,
|
|
96
|
+
// Pin kind/bucket/root LAST so out-of-schema model input can never override
|
|
97
|
+
// the authoritative bucket + sandbox root the author pinned. `root` may be
|
|
98
|
+
// a function of the ctx (per-run/owner isolation), resolved here.
|
|
99
|
+
execute: (input, context) => {
|
|
100
|
+
const root = typeof options.root === "function" ? options.root(context) : options.root ?? "";
|
|
101
|
+
return context.run(SANDBOX_REF, { ...input, bucket, kind: "fs", root });
|
|
102
|
+
},
|
|
103
|
+
inputSchema: FS_TOOL_SCHEMA,
|
|
104
|
+
isLunoraAgentTool: true,
|
|
105
|
+
needsApproval: options.needsApproval ?? defaultFsGate
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export { browserTool, containerTool, fsTool };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Telemetry } from 'ai';
|
|
2
|
+
/**
|
|
3
|
+
* Privacy options shared by every telemetry integration in this package.
|
|
4
|
+
*
|
|
5
|
+
* Both flags default to **FALSE**. Without an explicit opt-in, no prompt,
|
|
6
|
+
* message, tool input, generated model text, or tool output is ever forwarded
|
|
7
|
+
* to a downstream tracer — only structural metadata (model id, finish reason,
|
|
8
|
+
* token counts, tool name, timing, success/failure) is recorded. This is the
|
|
9
|
+
* privacy-safe default: turn recording on deliberately, per integration.
|
|
10
|
+
*/
|
|
11
|
+
interface CommonOptions {
|
|
12
|
+
/**
|
|
13
|
+
* When `true`, record model/tool **input** — prompts, messages, and tool
|
|
14
|
+
* call arguments. Default `false`.
|
|
15
|
+
*/
|
|
16
|
+
recordInputs?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* When `true`, record model/tool **output** — generated text and tool
|
|
19
|
+
* results. Default `false`.
|
|
20
|
+
*/
|
|
21
|
+
recordOutputs?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** The span handle a {@link BraintrustLike.traced} callback receives. */
|
|
24
|
+
interface BraintrustSpan {
|
|
25
|
+
/** Attach structured fields to the current span. */
|
|
26
|
+
log: (event: Record<string, unknown>) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The minimal, **structural** slice of the `braintrust` SDK this bridge needs.
|
|
30
|
+
* `braintrust` is intentionally **not** a dependency — the app passes its own
|
|
31
|
+
* initialized logger, so this package stays dependency-free and works with any
|
|
32
|
+
* compatible Braintrust SDK version.
|
|
33
|
+
*/
|
|
34
|
+
interface BraintrustLike {
|
|
35
|
+
/** Run `callback` inside a new traced span and return its result. */
|
|
36
|
+
traced: <T>(callback: (span: BraintrustSpan) => T, args?: {
|
|
37
|
+
name?: string;
|
|
38
|
+
type?: string;
|
|
39
|
+
}) => T;
|
|
40
|
+
}
|
|
41
|
+
/** Options for {@link braintrustTelemetry}. */
|
|
42
|
+
interface BraintrustTelemetryOptions extends CommonOptions {
|
|
43
|
+
/** Span-name prefix for the language-model call (e.g. the agent name). */
|
|
44
|
+
functionId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The caller's initialized Braintrust logger (dependency-injected). Import
|
|
47
|
+
* and initialize `braintrust` in your app and pass it as `logger`.
|
|
48
|
+
*/
|
|
49
|
+
logger: BraintrustLike;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A dependency-injected Braintrust bridge for the ai@7 telemetry surface.
|
|
53
|
+
*
|
|
54
|
+
* It wraps model calls (`type: "llm"`) and tool executions (`type: "tool"`) in
|
|
55
|
+
* `logger.traced` spans and logs structural metadata. Prompts / tool arguments
|
|
56
|
+
* are logged only when `recordInputs` is set; generated text / tool results
|
|
57
|
+
* only when `recordOutputs` is set. `onError` opens a span and logs the error.
|
|
58
|
+
*
|
|
59
|
+
* The app owns Braintrust initialization; pass the logger in as `logger`.
|
|
60
|
+
*/
|
|
61
|
+
declare const braintrustTelemetry: (options: BraintrustTelemetryOptions) => Telemetry;
|
|
62
|
+
/**
|
|
63
|
+
* Combine several {@link Telemetry} integrations into a single one that fans
|
|
64
|
+
* every lifecycle callback out to all of them and nests the two execution
|
|
65
|
+
* wrappers so multiple span-context wrappers compose correctly.
|
|
66
|
+
*
|
|
67
|
+
* Value callbacks (`onStart`, `onStepEnd`, `onToolExecutionEnd`, …) invoke each
|
|
68
|
+
* integration's matching callback (guarding `undefined`) and await them all in
|
|
69
|
+
* parallel. The wrappers (`executeLanguageModelCall`, `executeTool`) are
|
|
70
|
+
* composed by nesting right-to-left; integrations without a wrapper are skipped,
|
|
71
|
+
* and with none defined the plain `execute` still runs.
|
|
72
|
+
*/
|
|
73
|
+
declare const combineTelemetry: (...integrations: Telemetry[]) => Telemetry;
|
|
74
|
+
/** Severity level passed to a {@link ConsoleLogger}. */
|
|
75
|
+
type ConsoleLogLevel = "error" | "info" | "warn";
|
|
76
|
+
/**
|
|
77
|
+
* Structured log sink. Receives a level, a static human message, and a bag of
|
|
78
|
+
* structured fields (never interpolated into the message, so log processors can
|
|
79
|
+
* index them). The default sink writes to `globalThis.console`.
|
|
80
|
+
*/
|
|
81
|
+
type ConsoleLogger = (level: ConsoleLogLevel, message: string, fields: Record<string, unknown>) => void;
|
|
82
|
+
/** Options for {@link consoleTelemetry}. */
|
|
83
|
+
interface ConsoleTelemetryOptions extends CommonOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Identifier prefixed into every log message (e.g. the agent name). Helps
|
|
86
|
+
* correlate lines when several agents share one process. Optional.
|
|
87
|
+
*/
|
|
88
|
+
functionId?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Sink for structured log lines. Defaults to a `globalThis.console`-backed
|
|
91
|
+
* writer that routes `info`/`warn`/`error` to the matching console method.
|
|
92
|
+
*/
|
|
93
|
+
logger?: ConsoleLogger;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A zero-dependency structured tracer for the ai@7 telemetry surface.
|
|
97
|
+
*
|
|
98
|
+
* It maps the generation lifecycle onto {@link ConsoleLogger} calls: operation
|
|
99
|
+
* start/end, per-step start/end, model-call end (model + finish reason +
|
|
100
|
+
* usage), and tool start/end (name, success, timing). By default it records
|
|
101
|
+
* **only structural metadata** — set `recordInputs` to also log prompts / tool
|
|
102
|
+
* arguments, and `recordOutputs` to log generated text / tool results.
|
|
103
|
+
*
|
|
104
|
+
* Every callback is defensive (event fields may be absent) and synchronous.
|
|
105
|
+
*/
|
|
106
|
+
declare const consoleTelemetry: (options?: ConsoleTelemetryOptions) => Telemetry;
|
|
107
|
+
/**
|
|
108
|
+
* The minimal, **structural** slice of `@sentry/cloudflare` (equivalently
|
|
109
|
+
* `@sentry/node`/`@sentry/browser`) this bridge needs. `@sentry/cloudflare` is
|
|
110
|
+
* intentionally **not** a dependency — the app passes its own already-initialized
|
|
111
|
+
* Sentry namespace, so this package stays dependency-free and works with any
|
|
112
|
+
* compatible Sentry SDK version.
|
|
113
|
+
*/
|
|
114
|
+
interface SentryLike {
|
|
115
|
+
/** Capture a thrown value / exception. */
|
|
116
|
+
captureException: (exception: unknown) => unknown;
|
|
117
|
+
/** Run `callback` inside a new span and return its result. */
|
|
118
|
+
startSpan: <T>(context: {
|
|
119
|
+
attributes?: Record<string, unknown>;
|
|
120
|
+
name: string;
|
|
121
|
+
op?: string;
|
|
122
|
+
}, callback: (span: {
|
|
123
|
+
setStatus?: (status: {
|
|
124
|
+
code: number;
|
|
125
|
+
} | string) => void;
|
|
126
|
+
}) => T) => T;
|
|
127
|
+
}
|
|
128
|
+
/** Options for {@link sentryTelemetry}. */
|
|
129
|
+
interface SentryTelemetryOptions extends CommonOptions {
|
|
130
|
+
/** Span-name prefix for the language-model call (e.g. the agent name). */
|
|
131
|
+
functionId?: string;
|
|
132
|
+
/**
|
|
133
|
+
* The caller's already-initialized Sentry namespace (dependency-injected).
|
|
134
|
+
* `import * as Sentry from "@sentry/cloudflare"` and pass it as `Sentry`.
|
|
135
|
+
*/
|
|
136
|
+
Sentry: SentryLike;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A dependency-injected Sentry bridge for the ai@7 telemetry surface.
|
|
140
|
+
*
|
|
141
|
+
* It wraps model calls and tool executions in Sentry spans (via
|
|
142
|
+
* `Sentry.startSpan`) so nested provider/tool work is correctly parented, and
|
|
143
|
+
* routes `onError` to `Sentry.captureException`. Span attributes carry only
|
|
144
|
+
* structural metadata (model, provider, tool name) unless `recordInputs` is
|
|
145
|
+
* set, in which case prompts / tool arguments are attached too.
|
|
146
|
+
*
|
|
147
|
+
* The app owns Sentry initialization; pass the namespace in as `Sentry`.
|
|
148
|
+
*/
|
|
149
|
+
declare const sentryTelemetry: (options: SentryTelemetryOptions) => Telemetry;
|
|
150
|
+
export { type BraintrustLike, type BraintrustSpan, type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type SentryLike, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, sentryTelemetry };
|