anyclaude-sdk 0.8.1 → 0.10.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/README.md +38 -0
- package/dist/agent.d.ts +13 -1
- package/dist/agent.js +61 -3
- package/dist/loop.d.ts +3 -0
- package/dist/loop.js +30 -1
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.js +13 -0
- package/dist/query.d.ts +8 -0
- package/dist/query.js +3 -0
- package/dist/team/broadcast-mailbox.d.ts +34 -0
- package/dist/team/broadcast-mailbox.js +85 -0
- package/dist/team/index.d.ts +1 -0
- package/dist/team/index.js +1 -0
- package/dist/team/mailbox.d.ts +5 -2
- package/dist/team/mailbox.js +7 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.js +1 -1
- package/dist/tools/define.d.ts +2 -0
- package/dist/tools/define.js +2 -0
- package/dist/tools/types.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -198,6 +198,26 @@ Optional off-main-thread execution via a Comlink worker harness
|
|
|
198
198
|
query({ prompt, workspace, llm, agents: {}, background: true })
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
### Agents in separate Web Workers
|
|
202
|
+
|
|
203
|
+
Two halves: **Comlink** for main→worker control (`wrapWorker` / `exposeBackgroundWorker`,
|
|
204
|
+
above), and **`BroadcastChannelMailbox`** so agents in *different* workers gossip
|
|
205
|
+
mailbox-style. It's a drop-in `Mailbox`, so the existing `team` tools
|
|
206
|
+
(`send_message` / `dispatch_tasks`) work unchanged across workers:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { BroadcastChannelMailbox } from 'anyclaude-sdk'
|
|
210
|
+
|
|
211
|
+
// inside each Web Worker / tab / worker_thread, same channel name:
|
|
212
|
+
const mailbox = new BroadcastChannelMailbox({ channelName: 'team', origin: 'planner' })
|
|
213
|
+
query({ prompt, workspace, llm, team: true, mailbox })
|
|
214
|
+
// messages sent by one worker land in the addressed agent's inbox in another.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Uses the global `BroadcastChannel` by default; pass `{ channel }` (e.g. the
|
|
218
|
+
[`broadcast-channel`](https://www.npmjs.com/package/broadcast-channel) package)
|
|
219
|
+
for cross-tab durability or older runtimes.
|
|
220
|
+
|
|
201
221
|
## Pluggable backends
|
|
202
222
|
|
|
203
223
|
You aren't tied to WebContainer. A `Sandbox` is just a `FileSystem` plus a
|
|
@@ -400,6 +420,24 @@ query({ prompt, workspace, llm,
|
|
|
400
420
|
|
|
401
421
|
Only the lean core + `tool_search` are sent each turn; the model searches when it needs a niche tool, the SDK arms it, and the call goes through. Register 35, send ~10.
|
|
402
422
|
|
|
423
|
+
## Agent-loop tuning (cheap / lightweight / fast)
|
|
424
|
+
|
|
425
|
+
Opt-in knobs for token cost and latency — especially on weak / uncached models:
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
query({
|
|
429
|
+
prompt, workspace, llm,
|
|
430
|
+
systemPromptPreset: 'lean', // ~70% shorter built-in prompt — saved every turn on uncached models
|
|
431
|
+
keepToolResults: 6, // context editing: stub tool_results older than the last 6 (caps transcript growth)
|
|
432
|
+
parallelToolExecution: true, // run a turn's read-only tool calls concurrently (~2× faster on multi-read turns)
|
|
433
|
+
deferredTools: [/* niche tools */], // keep rarely-used tools out of the payload until tool_search arms them
|
|
434
|
+
})
|
|
435
|
+
// custom read tool opting into parallelism:
|
|
436
|
+
defineTool({ name: 'get_logs', description: '…', parameters, run, parallelSafe: true })
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Mutating tools / `bash` / delegated client tools always execute serially; `keepToolResults` and `parallelToolExecution` preserve correctness, just trim cost/latency.
|
|
440
|
+
|
|
403
441
|
## Other niceties
|
|
404
442
|
|
|
405
443
|
- **Live compaction marker** — `autoCompact` emits a `compact_boundary` with `status: 'start'` *before* summarizing (for a live "compacting…" shimmer) and `status: 'end'` after with `post_tokens`.
|
package/dist/agent.d.ts
CHANGED
|
@@ -18,8 +18,12 @@ export interface AgentOptions {
|
|
|
18
18
|
/** Custom tools ADDED to the builtins (or to `tools` if given). Use `defineTool`. */
|
|
19
19
|
extraTools?: Tool[];
|
|
20
20
|
model?: string;
|
|
21
|
-
/** Full system prompt. If omitted, the
|
|
21
|
+
/** Full system prompt. If omitted, the built-in prompt for `systemPromptPreset` is used. */
|
|
22
22
|
systemPrompt?: string;
|
|
23
|
+
/** Which built-in system prompt to use when `systemPrompt` is omitted: `'default'`
|
|
24
|
+
* (full Claude-Code contract) or `'lean'` (much shorter — cheaper every turn on
|
|
25
|
+
* weak/uncached models). Default `'default'`. */
|
|
26
|
+
systemPromptPreset?: 'default' | 'lean';
|
|
23
27
|
/** Text appended after the (default or custom) system prompt. */
|
|
24
28
|
appendSystemPrompt?: string;
|
|
25
29
|
/** Allowlist of tool names. When set, only these tools are exposed. */
|
|
@@ -31,6 +35,14 @@ export interface AgentOptions {
|
|
|
31
35
|
* until the model searches and the loop arms them. For large pools of
|
|
32
36
|
* rarely-used integration tools. (Per-tool `defer: true` works too.) */
|
|
33
37
|
deferredTools?: string[];
|
|
38
|
+
/** Context editing: keep only the most recent N tool_result messages verbatim;
|
|
39
|
+
* older ones are replaced with a short stub before each LLM call. Caps transcript
|
|
40
|
+
* growth on long runs. Off when undefined. (Trades prompt-cache hits on the cleared
|
|
41
|
+
* span for fewer tokens — a clear win on uncached endpoints.) */
|
|
42
|
+
keepToolResults?: number;
|
|
43
|
+
/** Execute a turn's tool calls concurrently when they're all read-only + server-run
|
|
44
|
+
* (mutating tools / bash / delegated stay sequential). Latency win on multi-read turns. */
|
|
45
|
+
parallelToolExecution?: boolean;
|
|
34
46
|
maxTurns?: number;
|
|
35
47
|
/** Wall-clock budget (ms). At a turn boundary past this, the loop pauses: it
|
|
36
48
|
* persists to sessionStore and emits a `paused` system message instead of
|
package/dist/agent.js
CHANGED
|
@@ -20,7 +20,7 @@ import { PLAN_MODE_TOOLS } from './tools/plan_mode.js';
|
|
|
20
20
|
import { rulesToCanUseTool, ruleSetFromStrings, applyPermissionUpdate, isReadOnlyTool, } from './permissions/index.js';
|
|
21
21
|
import { loadSettings, settingsToPermissionRuleSet } from './settings/index.js';
|
|
22
22
|
import { loadSkillsFromFs, skillsToCommands, skill as skillTool } from './skills/index.js';
|
|
23
|
-
import {
|
|
23
|
+
import { defaultSubagentPrompt, systemPromptFor } from './prompt.js';
|
|
24
24
|
import { DEFAULT_MAX_RESULT_CHARS, maybePersistLargeResult } from './persist.js';
|
|
25
25
|
import { computeCostUSD, contextWindowFor } from './util/pricing.js';
|
|
26
26
|
import { estimateTokens, summarizeHistory } from './compact.js';
|
|
@@ -282,7 +282,7 @@ export async function* runAgent(options) {
|
|
|
282
282
|
const named = names.length ? `|<(?:${names.join('|')})[\\s/>]` : '';
|
|
283
283
|
return new RegExp(`<tool_call|<function\\s*=|<thinking${named}`, 'i');
|
|
284
284
|
})();
|
|
285
|
-
let system = options.systemPrompt != null ? options.systemPrompt :
|
|
285
|
+
let system = options.systemPrompt != null ? options.systemPrompt : systemPromptFor(cwd, options.systemPromptPreset);
|
|
286
286
|
if (teamEnabled)
|
|
287
287
|
system += '\n\n' + coordinatorPrompt();
|
|
288
288
|
if (memory) {
|
|
@@ -293,6 +293,26 @@ export async function* runAgent(options) {
|
|
|
293
293
|
if (options.appendSystemPrompt)
|
|
294
294
|
system += '\n\n' + options.appendSystemPrompt;
|
|
295
295
|
const history = [{ role: 'system', content: system }];
|
|
296
|
+
// Context editing: keep the most recent N tool_result messages verbatim; replace
|
|
297
|
+
// older ones with a short stub (idempotent) so they stop costing tokens each turn.
|
|
298
|
+
const keepToolResults = options.keepToolResults;
|
|
299
|
+
const CLEARED_STUB = '[earlier tool output cleared to save context]';
|
|
300
|
+
const pruneToolResults = () => {
|
|
301
|
+
if (keepToolResults == null || keepToolResults < 0)
|
|
302
|
+
return;
|
|
303
|
+
const toolIdx = [];
|
|
304
|
+
for (let i = 0; i < history.length; i++)
|
|
305
|
+
if (history[i].role === 'tool')
|
|
306
|
+
toolIdx.push(i);
|
|
307
|
+
const cutoff = toolIdx.length - keepToolResults;
|
|
308
|
+
for (let j = 0; j < cutoff; j++) {
|
|
309
|
+
const m = history[toolIdx[j]];
|
|
310
|
+
if (typeof m.content === 'string' && m.content !== CLEARED_STUB)
|
|
311
|
+
m.content = CLEARED_STUB;
|
|
312
|
+
else if (Array.isArray(m.content))
|
|
313
|
+
m.content = CLEARED_STUB;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
296
316
|
const store = { todos: [] };
|
|
297
317
|
const ctx = {
|
|
298
318
|
fs: workspace,
|
|
@@ -645,6 +665,9 @@ export async function* runAgent(options) {
|
|
|
645
665
|
await runHooks('PostCompact', { hook_event_name: 'PostCompact', trigger: 'auto' });
|
|
646
666
|
}
|
|
647
667
|
}
|
|
668
|
+
// Context editing: stub out all but the most recent N tool_result messages so
|
|
669
|
+
// old tool output stops costing tokens on every subsequent turn.
|
|
670
|
+
pruneToolResults();
|
|
648
671
|
let streamedText = '';
|
|
649
672
|
let captured = [];
|
|
650
673
|
const apiStart = Date.now();
|
|
@@ -760,6 +783,28 @@ export async function* runAgent(options) {
|
|
|
760
783
|
const toolResultBlocks = [];
|
|
761
784
|
clientRequests = [];
|
|
762
785
|
const turnMedia = [];
|
|
786
|
+
// Parallel tool execution: when every call this turn is read-only + server-run,
|
|
787
|
+
// kick off the runs concurrently up front; the sequential loop below still does
|
|
788
|
+
// permission/hooks/assembly in order but awaits these prefetched results instead
|
|
789
|
+
// of running serially. Read-only ⇒ no ordering/side-effect risk. (Mutating tools,
|
|
790
|
+
// bash, and delegated client tools fall through to the normal serial path.)
|
|
791
|
+
const prefetch = new Map();
|
|
792
|
+
if (options.parallelToolExecution &&
|
|
793
|
+
calls.length > 1 &&
|
|
794
|
+
calls.every((c) => {
|
|
795
|
+
const t = byName.get(c.function.name);
|
|
796
|
+
if (clientTools.has(c.function.name) || !t?.run)
|
|
797
|
+
return false;
|
|
798
|
+
return t.parallelSafe === true || isReadOnlyTool(c.function.name, safeParse(c.function.arguments));
|
|
799
|
+
})) {
|
|
800
|
+
for (const c of calls) {
|
|
801
|
+
const t = byName.get(c.function.name);
|
|
802
|
+
const input = safeParse(c.function.arguments);
|
|
803
|
+
prefetch.set(c.id, Promise.resolve()
|
|
804
|
+
.then(() => t.run(input, ctx))
|
|
805
|
+
.then((r) => ({ r }), (e) => ({ e })));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
763
808
|
for (const call of calls) {
|
|
764
809
|
if (signal?.aborted)
|
|
765
810
|
break;
|
|
@@ -872,10 +917,23 @@ export async function* runAgent(options) {
|
|
|
872
917
|
abortController?.abort();
|
|
873
918
|
}
|
|
874
919
|
else {
|
|
920
|
+
const inputChanged = !!('updatedInput' in decision && decision.updatedInput);
|
|
875
921
|
if ('updatedInput' in decision && decision.updatedInput)
|
|
876
922
|
input = decision.updatedInput;
|
|
877
923
|
try {
|
|
878
|
-
|
|
924
|
+
// Use the concurrently-prefetched result when present and the input
|
|
925
|
+
// wasn't rewritten by permission; otherwise run now.
|
|
926
|
+
const pf = !inputChanged ? prefetch.get(call.id) : undefined;
|
|
927
|
+
let r;
|
|
928
|
+
if (pf) {
|
|
929
|
+
const out = await pf;
|
|
930
|
+
if (out.e !== undefined)
|
|
931
|
+
throw out.e;
|
|
932
|
+
r = out.r;
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
r = await tool.run(input, ctx);
|
|
936
|
+
}
|
|
879
937
|
content = r.content;
|
|
880
938
|
isError = !!r.isError;
|
|
881
939
|
}
|
package/dist/loop.d.ts
CHANGED
|
@@ -42,6 +42,9 @@ export interface RunToolLoopOptions {
|
|
|
42
42
|
* Default `true`. Set `false` to pass raw args straight through.
|
|
43
43
|
*/
|
|
44
44
|
repairToolCalls?: boolean;
|
|
45
|
+
/** Run a turn's tool calls concurrently when all are read-only + server-run
|
|
46
|
+
* (mutating/bash/delegated stay serial). Latency win on multi-read turns. */
|
|
47
|
+
parallelToolExecution?: boolean;
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* Run the bare tool loop, yielding SDKMessages until the model stops or maxTurns.
|
package/dist/loop.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { toolByName, toolDefs } from './tools/index.js';
|
|
2
2
|
import { validateToolArguments } from './llm/repair.js';
|
|
3
3
|
import { parseToolCalls } from './llm/dialects.js';
|
|
4
|
+
import { isReadOnlyTool } from './permissions/index.js';
|
|
4
5
|
import { uuid } from './util/ids.js';
|
|
5
6
|
/** Regex that matches the onset of tool-call / reasoning markup in streamed text. */
|
|
6
7
|
function buildSuppressRe(toolNames) {
|
|
@@ -202,6 +203,23 @@ export async function* runToolLoop(opts) {
|
|
|
202
203
|
break;
|
|
203
204
|
const toolResultBlocks = [];
|
|
204
205
|
const turnMedia = [];
|
|
206
|
+
// Parallel tool execution: when all calls this turn are read-only + server-run,
|
|
207
|
+
// run them concurrently up front; the loop assembles results in order below.
|
|
208
|
+
const prefetch = new Map();
|
|
209
|
+
if (opts.parallelToolExecution &&
|
|
210
|
+
calls.length > 1 &&
|
|
211
|
+
calls.every((c) => {
|
|
212
|
+
const t = byName.get(c.function.name);
|
|
213
|
+
if (clientTools.has(c.function.name) || !t?.run)
|
|
214
|
+
return false;
|
|
215
|
+
return t.parallelSafe === true || isReadOnlyTool(c.function.name, safeParse(c.function.arguments));
|
|
216
|
+
})) {
|
|
217
|
+
for (const c of calls) {
|
|
218
|
+
const t = byName.get(c.function.name);
|
|
219
|
+
const input = safeParse(c.function.arguments);
|
|
220
|
+
prefetch.set(c.id, Promise.resolve().then(() => t.run(input, ctx)).then((r) => ({ r }), (e) => ({ e })));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
205
223
|
for (const call of calls) {
|
|
206
224
|
if (signal?.aborted)
|
|
207
225
|
break;
|
|
@@ -254,10 +272,21 @@ export async function* runToolLoop(opts) {
|
|
|
254
272
|
isError = true;
|
|
255
273
|
}
|
|
256
274
|
else {
|
|
275
|
+
const inputChanged = !!('updatedInput' in decision && decision.updatedInput);
|
|
257
276
|
if ('updatedInput' in decision && decision.updatedInput)
|
|
258
277
|
input = decision.updatedInput;
|
|
259
278
|
try {
|
|
260
|
-
const
|
|
279
|
+
const pf = !inputChanged ? prefetch.get(call.id) : undefined;
|
|
280
|
+
let r;
|
|
281
|
+
if (pf) {
|
|
282
|
+
const out = await pf;
|
|
283
|
+
if (out.e !== undefined)
|
|
284
|
+
throw out.e;
|
|
285
|
+
r = out.r;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
r = await tool.run(input, ctx);
|
|
289
|
+
}
|
|
261
290
|
content = r.content;
|
|
262
291
|
isError = !!r.isError;
|
|
263
292
|
}
|
package/dist/prompt.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export declare function defaultSystemPrompt(cwd: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* Lean system prompt — a much shorter contract for token-sensitive / weak models
|
|
4
|
+
* (and uncached endpoints, where the prompt is paid every turn). Keeps only the
|
|
5
|
+
* load-bearing rules: read-before-edit, exact edit matching, stop when done.
|
|
6
|
+
*/
|
|
7
|
+
export declare function leanSystemPrompt(cwd: string): string;
|
|
8
|
+
/** Pick the built-in system prompt by preset. */
|
|
9
|
+
export declare function systemPromptFor(cwd: string, preset?: 'default' | 'lean'): string;
|
|
2
10
|
/**
|
|
3
11
|
* Default system prompt for a general-purpose sub-agent spawned via the `task`
|
|
4
12
|
* tool. The sub-agent runs autonomously and returns only its final answer.
|
package/dist/prompt.js
CHANGED
|
@@ -21,6 +21,19 @@ You have access to tools for reading, writing, and editing files, running shell
|
|
|
21
21
|
|
|
22
22
|
When the task is complete, stop calling tools and give a short summary of what you did.`;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Lean system prompt — a much shorter contract for token-sensitive / weak models
|
|
26
|
+
* (and uncached endpoints, where the prompt is paid every turn). Keeps only the
|
|
27
|
+
* load-bearing rules: read-before-edit, exact edit matching, stop when done.
|
|
28
|
+
*/
|
|
29
|
+
export function leanSystemPrompt(cwd) {
|
|
30
|
+
return `You are a coding agent working on a real workspace (files + shell) via tools. Working directory: ${cwd}.
|
|
31
|
+
Rules: read a file with read_file before edit_file; edit_file old_string must match exactly (else add context or use replace_all); prefer file tools over cat/sed; batch independent tool calls. Be concise. When done, stop calling tools and give a one-line summary.`;
|
|
32
|
+
}
|
|
33
|
+
/** Pick the built-in system prompt by preset. */
|
|
34
|
+
export function systemPromptFor(cwd, preset) {
|
|
35
|
+
return preset === 'lean' ? leanSystemPrompt(cwd) : defaultSystemPrompt(cwd);
|
|
36
|
+
}
|
|
24
37
|
/**
|
|
25
38
|
* Default system prompt for a general-purpose sub-agent spawned via the `task`
|
|
26
39
|
* tool. The sub-agent runs autonomously and returns only its final answer.
|
package/dist/query.d.ts
CHANGED
|
@@ -19,12 +19,20 @@ export interface QueryOptions {
|
|
|
19
19
|
extraTools?: Tool[];
|
|
20
20
|
model?: string;
|
|
21
21
|
systemPrompt?: string;
|
|
22
|
+
/** Built-in prompt when `systemPrompt` is omitted: `'default'` or `'lean'` (shorter,
|
|
23
|
+
* cheaper every turn on weak/uncached models). */
|
|
24
|
+
systemPromptPreset?: 'default' | 'lean';
|
|
22
25
|
appendSystemPrompt?: string;
|
|
23
26
|
allowedTools?: string[];
|
|
24
27
|
disallowedTools?: string[];
|
|
25
28
|
/** Tool names to defer out of the per-turn payload — discoverable via `tool_search`
|
|
26
29
|
* and armed on demand. Saves tokens on large tool pools (also per-tool `defer: true`). */
|
|
27
30
|
deferredTools?: string[];
|
|
31
|
+
/** Context editing: keep only the most recent N tool_result messages verbatim; older
|
|
32
|
+
* ones are stubbed before each LLM call. Caps transcript growth on long runs. */
|
|
33
|
+
keepToolResults?: number;
|
|
34
|
+
/** Run a turn's read-only tool calls concurrently (mutating/bash/delegated stay serial). */
|
|
35
|
+
parallelToolExecution?: boolean;
|
|
28
36
|
maxTurns?: number;
|
|
29
37
|
/** Wall-clock budget (ms): pause at a turn boundary past this + emit `paused` (survivor). */
|
|
30
38
|
maxDurationMs?: number;
|
package/dist/query.js
CHANGED
|
@@ -18,10 +18,13 @@ export function query(options) {
|
|
|
18
18
|
extraTools: options.extraTools,
|
|
19
19
|
model: options.model,
|
|
20
20
|
systemPrompt: options.systemPrompt,
|
|
21
|
+
systemPromptPreset: options.systemPromptPreset,
|
|
21
22
|
appendSystemPrompt: options.appendSystemPrompt,
|
|
22
23
|
allowedTools: options.allowedTools,
|
|
23
24
|
disallowedTools: options.disallowedTools,
|
|
24
25
|
deferredTools: options.deferredTools,
|
|
26
|
+
keepToolResults: options.keepToolResults,
|
|
27
|
+
parallelToolExecution: options.parallelToolExecution,
|
|
25
28
|
maxTurns: options.maxTurns,
|
|
26
29
|
maxDurationMs: options.maxDurationMs,
|
|
27
30
|
continueRun: options.continueRun,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Mailbox } from './mailbox.js';
|
|
2
|
+
/** Minimal structural type for a BroadcastChannel-like transport, so callers
|
|
3
|
+
* can inject the `broadcast-channel` polyfill or any compatible object. */
|
|
4
|
+
export interface ChannelLike {
|
|
5
|
+
postMessage(data: unknown): void;
|
|
6
|
+
/** Native BroadcastChannel uses an `onmessage` setter with `{data}` events. */
|
|
7
|
+
onmessage?: ((ev: {
|
|
8
|
+
data: unknown;
|
|
9
|
+
}) => void) | null;
|
|
10
|
+
/** `broadcast-channel` polyfill uses addEventListener('message', fn). */
|
|
11
|
+
addEventListener?: (type: 'message', fn: (data: unknown) => void) => void;
|
|
12
|
+
close?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface BroadcastChannelMailboxOptions {
|
|
15
|
+
/** Channel name (default 'anyclaude-team'). Ignored if `channel` is given. */
|
|
16
|
+
channelName?: string;
|
|
17
|
+
/** Inject a ready-made channel (e.g. the `broadcast-channel` package) or a
|
|
18
|
+
* custom BroadcastChannel factory. When omitted, the global BroadcastChannel
|
|
19
|
+
* is used. */
|
|
20
|
+
channel?: ChannelLike;
|
|
21
|
+
/** Stable origin id for this instance; defaults to a random id. Set it to a
|
|
22
|
+
* worker/agent name if you want deterministic, debuggable message ids. */
|
|
23
|
+
origin?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class BroadcastChannelMailbox extends Mailbox {
|
|
26
|
+
private readonly channel;
|
|
27
|
+
private readonly origin;
|
|
28
|
+
private closed;
|
|
29
|
+
constructor(opts?: BroadcastChannelMailboxOptions);
|
|
30
|
+
/** Send a message: append to the local replica and broadcast to peers. */
|
|
31
|
+
send(from: string, to: string, text: string): string;
|
|
32
|
+
/** Stop listening and release the channel. Safe to call more than once. */
|
|
33
|
+
close(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// A Mailbox that gossips across execution contexts (Web Workers, browser tabs,
|
|
2
|
+
// Node worker_threads) over a BroadcastChannel. Drop-in for the in-memory
|
|
3
|
+
// Mailbox: the existing `team` tools (send_message / dispatch_tasks) work
|
|
4
|
+
// unchanged, but messages now propagate to every agent on the same channel.
|
|
5
|
+
//
|
|
6
|
+
// Design: each instance keeps its own eventually-consistent replica of the
|
|
7
|
+
// message stream. `send()` appends locally AND posts on the channel; every
|
|
8
|
+
// other instance ingests the inbound message into its replica (de-duped by a
|
|
9
|
+
// globally-unique id). `inbox()`/`all()`/`markRead()` read/mutate the local
|
|
10
|
+
// replica synchronously — read state is per-instance (each agent tracks what
|
|
11
|
+
// *it* has seen), which is exactly the inbox semantics we want.
|
|
12
|
+
//
|
|
13
|
+
// Browser-safe: BroadcastChannel is a Web API (also a Node >=15 global inside
|
|
14
|
+
// worker_threads). For older runtimes or cross-tab durability, inject the
|
|
15
|
+
// `broadcast-channel` npm package's channel via the `channel` option.
|
|
16
|
+
import { Mailbox } from './mailbox.js';
|
|
17
|
+
function isWire(d) {
|
|
18
|
+
return !!d && typeof d === 'object' && d.__ac === 'msg';
|
|
19
|
+
}
|
|
20
|
+
function randomId() {
|
|
21
|
+
const g = globalThis;
|
|
22
|
+
if (g.crypto?.randomUUID)
|
|
23
|
+
return g.crypto.randomUUID().slice(0, 8);
|
|
24
|
+
// Fallback without Math.random reliance: derive from high-res-ish counters.
|
|
25
|
+
return 'o' + (Date.now().toString(36) + (originSeed++).toString(36));
|
|
26
|
+
}
|
|
27
|
+
let originSeed = 0;
|
|
28
|
+
export class BroadcastChannelMailbox extends Mailbox {
|
|
29
|
+
constructor(opts = {}) {
|
|
30
|
+
super();
|
|
31
|
+
this.closed = false;
|
|
32
|
+
this.origin = opts.origin ?? randomId();
|
|
33
|
+
let ch = opts.channel;
|
|
34
|
+
if (!ch) {
|
|
35
|
+
const BC = globalThis
|
|
36
|
+
.BroadcastChannel;
|
|
37
|
+
if (!BC) {
|
|
38
|
+
throw new Error('BroadcastChannel is not available in this runtime. Pass `channel` ' +
|
|
39
|
+
'(e.g. from the `broadcast-channel` npm package) to BroadcastChannelMailbox.');
|
|
40
|
+
}
|
|
41
|
+
ch = new BC(opts.channelName ?? 'anyclaude-team');
|
|
42
|
+
}
|
|
43
|
+
this.channel = ch;
|
|
44
|
+
// Normalize both delivery shapes: native BroadcastChannel hands the listener
|
|
45
|
+
// a MessageEvent ({data}); the `broadcast-channel` polyfill hands it the raw
|
|
46
|
+
// payload. Accept either, then de-dupe via the protected ingest().
|
|
47
|
+
const handle = (arg) => {
|
|
48
|
+
if (this.closed)
|
|
49
|
+
return;
|
|
50
|
+
let data = arg;
|
|
51
|
+
if (!isWire(arg) && arg && typeof arg === 'object' && 'data' in arg) {
|
|
52
|
+
data = arg.data;
|
|
53
|
+
}
|
|
54
|
+
if (isWire(data) && data.origin !== this.origin)
|
|
55
|
+
this.ingest(data.message);
|
|
56
|
+
};
|
|
57
|
+
// Single registration: addEventListener when available (native + polyfill
|
|
58
|
+
// both support it), otherwise the onmessage setter.
|
|
59
|
+
if (typeof this.channel.addEventListener === 'function') {
|
|
60
|
+
this.channel.addEventListener('message', handle);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.channel.onmessage = (ev) => handle(ev);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Send a message: append to the local replica and broadcast to peers. */
|
|
67
|
+
send(from, to, text) {
|
|
68
|
+
// Globally-unique id: origin-scoped so two workers never collide.
|
|
69
|
+
const id = `msg_${this.origin}_${++this.counter}`;
|
|
70
|
+
const m = { id, from, to, text, ts: Date.now(), read: false };
|
|
71
|
+
this.ingest(m);
|
|
72
|
+
if (!this.closed) {
|
|
73
|
+
const wire = { __ac: 'msg', origin: this.origin, message: m };
|
|
74
|
+
this.channel.postMessage(wire);
|
|
75
|
+
}
|
|
76
|
+
return id;
|
|
77
|
+
}
|
|
78
|
+
/** Stop listening and release the channel. Safe to call more than once. */
|
|
79
|
+
close() {
|
|
80
|
+
if (this.closed)
|
|
81
|
+
return;
|
|
82
|
+
this.closed = true;
|
|
83
|
+
this.channel.close?.();
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/team/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Mailbox, type AgentMessage } from './mailbox.js';
|
|
2
|
+
export { BroadcastChannelMailbox, type ChannelLike, type BroadcastChannelMailboxOptions, } from './broadcast-mailbox.js';
|
|
2
3
|
export { TaskBoard, type BoardTask, type TaskStatus } from './taskBoard.js';
|
|
3
4
|
export { TEAM_TOOLS, sendMessage, taskCreate, taskUpdate, taskGet, boardList, } from './tools.js';
|
|
4
5
|
export { coordinatorPrompt } from './prompt.js';
|
package/dist/team/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Teammate/coordinator MVP: shared in-memory mailbox + task board, the tools
|
|
2
2
|
// the coordinator uses to delegate, and the coordinator system-prompt addendum.
|
|
3
3
|
export { Mailbox } from './mailbox.js';
|
|
4
|
+
export { BroadcastChannelMailbox, } from './broadcast-mailbox.js';
|
|
4
5
|
export { TaskBoard } from './taskBoard.js';
|
|
5
6
|
export { TEAM_TOOLS, sendMessage, taskCreate, taskUpdate, taskGet, boardList, } from './tools.js';
|
|
6
7
|
export { coordinatorPrompt } from './prompt.js';
|
package/dist/team/mailbox.d.ts
CHANGED
|
@@ -7,10 +7,13 @@ export type AgentMessage = {
|
|
|
7
7
|
read: boolean;
|
|
8
8
|
};
|
|
9
9
|
export declare class Mailbox {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
protected messages: AgentMessage[];
|
|
11
|
+
protected counter: number;
|
|
12
12
|
/** Deliver a message; returns the message id. */
|
|
13
13
|
send(from: string, to: string, text: string): string;
|
|
14
|
+
/** Append a message produced elsewhere (e.g. another worker), de-duped by id.
|
|
15
|
+
* Subclasses that sync over a transport use this for inbound messages. */
|
|
16
|
+
protected ingest(m: AgentMessage): void;
|
|
14
17
|
/** Messages addressed to `agentId`, oldest first. */
|
|
15
18
|
inbox(agentId: string, opts?: {
|
|
16
19
|
unreadOnly?: boolean;
|
package/dist/team/mailbox.js
CHANGED
|
@@ -12,6 +12,13 @@ export class Mailbox {
|
|
|
12
12
|
this.messages.push({ id, from, to, text, ts: Date.now(), read: false });
|
|
13
13
|
return id;
|
|
14
14
|
}
|
|
15
|
+
/** Append a message produced elsewhere (e.g. another worker), de-duped by id.
|
|
16
|
+
* Subclasses that sync over a transport use this for inbound messages. */
|
|
17
|
+
ingest(m) {
|
|
18
|
+
if (this.messages.some((x) => x.id === m.id))
|
|
19
|
+
return;
|
|
20
|
+
this.messages.push(m);
|
|
21
|
+
}
|
|
15
22
|
/** Messages addressed to `agentId`, oldest first. */
|
|
16
23
|
inbox(agentId, opts) {
|
|
17
24
|
return this.messages.filter((m) => m.to === agentId && (!opts?.unreadOnly || !m.read));
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Bump on release so adoption can be bucketed by version. */
|
|
2
|
-
export declare const TELEMETRY_SDK_VERSION = "0.
|
|
2
|
+
export declare const TELEMETRY_SDK_VERSION = "0.10.0";
|
|
3
3
|
export interface TelemetryOptions {
|
|
4
4
|
/** Force-disable for this call (highest precedence besides the global opt-outs). */
|
|
5
5
|
disabled?: boolean;
|
package/dist/telemetry.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// See TELEMETRY.md for the full disclosure.
|
|
17
17
|
import { uuid } from './util/ids.js';
|
|
18
18
|
/** Bump on release so adoption can be bucketed by version. */
|
|
19
|
-
export const TELEMETRY_SDK_VERSION = '0.
|
|
19
|
+
export const TELEMETRY_SDK_VERSION = '0.10.0';
|
|
20
20
|
// Aggregate-only collector (Puter Worker; see examples/telemetry-collector).
|
|
21
21
|
// Override with `ANYCLAUDE_TELEMETRY_URL` / `telemetry: { url }`, or disable
|
|
22
22
|
// entirely with the opt-outs above. Set to '' to make telemetry a no-op.
|
package/dist/tools/define.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface DefineToolSpec {
|
|
|
18
18
|
/** Defer out of the per-turn payload — discoverable via `tool_search`, armed on
|
|
19
19
|
* demand. For large pools of rarely-used tools (see Tool.defer). */
|
|
20
20
|
defer?: boolean;
|
|
21
|
+
/** Safe to run concurrently with other calls in a turn (see Tool.parallelSafe). */
|
|
22
|
+
parallelSafe?: boolean;
|
|
21
23
|
}
|
|
22
24
|
/** Build a `Tool` from a friendly spec. */
|
|
23
25
|
export declare function defineTool(spec: DefineToolSpec): Tool;
|
package/dist/tools/define.js
CHANGED
package/dist/tools/types.d.ts
CHANGED
|
@@ -103,4 +103,11 @@ export interface Tool {
|
|
|
103
103
|
* rarely-used integration tools. (Also settable via `query({ deferredTools })`.)
|
|
104
104
|
*/
|
|
105
105
|
defer?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Mark this tool safe to execute CONCURRENTLY with other calls in the same turn
|
|
108
|
+
* (no side effects / no ordering dependency) — opts it into `parallelToolExecution`
|
|
109
|
+
* even if it isn't a recognized read-only builtin. Set on custom read tools
|
|
110
|
+
* (e.g. get_console_logs, screenshot, fetch-status).
|
|
111
|
+
*/
|
|
112
|
+
parallelSafe?: boolean;
|
|
106
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anyclaude-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Standalone, browser-compatible SDK providing Claude Code agent capabilities (tools, tool loop, multi-turn, MCP, sub-agents, sessions) against any OpenAI/Anthropic-compatible LLM endpoint. Runs in the browser (WebContainer), Node, and Bun — no backend required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|