anyclaude-sdk 0.4.7 → 0.4.9

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/dist/agent.d.ts CHANGED
@@ -38,6 +38,11 @@ export interface AgentOptions {
38
38
  * one, the loop emits a `client_tool_request` + pauses; the client runs it and
39
39
  * resumes (continueRun) with `clientToolResults`. (e.g. bash on a browser WebContainer.) */
40
40
  clientTools?: string[];
41
+ /** One switch to delegate ALL built-in workspace tools (bash + file ops) to the
42
+ * host: the server emits client_tool_request for them and NEVER runs them
43
+ * against its own (in-memory) workspace — execution happens client-side
44
+ * (e.g. a browser WebContainer / IndexedDB via createWorkspaceClientTools). */
45
+ clientWorkspaceTools?: boolean;
41
46
  /** Results for client-tool calls, injected into the transcript before continuing. */
42
47
  clientToolResults?: Array<{
43
48
  tool_use_id: string;
package/dist/agent.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // 5. Execute each tool against the workspace
9
9
  // 6. Run PostToolUse hooks; append results to the message history
10
10
  // 7. Repeat until no tool calls or max turns reached
11
- import { ALL_CLAUDE_CODE_TOOLS, toolByName, toolDefs } from './tools/index.js';
11
+ import { ALL_CLAUDE_CODE_TOOLS, toolByName, toolDefs, WORKSPACE_TOOL_NAMES } from './tools/index.js';
12
12
  import { task as taskTool } from './tools/task.js';
13
13
  import { askUserQuestion } from './tools/ask_user.js';
14
14
  import { loadMcpServers } from './mcp/index.js';
@@ -250,6 +250,16 @@ export async function* runAgent(options) {
250
250
  tools = [...localTools, ...loaded.tools];
251
251
  mcpStatuses = loaded.statuses.map((s) => ({ name: s.name, status: s.status }));
252
252
  }
253
+ // Delegation set: explicit clientTools, plus (one switch) every built-in
254
+ // workspace tool when clientWorkspaceTools is on, plus any tool with no `run`
255
+ // (Vercel-style "no execute = client"). These emit client_tool_request and are
256
+ // never executed on the server.
257
+ if (options.clientWorkspaceTools)
258
+ for (const n of WORKSPACE_TOOL_NAMES)
259
+ clientTools.add(n);
260
+ for (const t of tools)
261
+ if (!t.run)
262
+ clientTools.add(t.def.function.name);
253
263
  const defs = toolDefs(tools);
254
264
  const byName = toolByName(tools);
255
265
  let system = options.systemPrompt != null ? options.systemPrompt : defaultSystemPrompt(cwd);
@@ -707,8 +717,11 @@ export async function* runAgent(options) {
707
717
  let content = '';
708
718
  let isError = false;
709
719
  let extraContext = '';
710
- if (!tool) {
711
- content = `Error: unknown tool "${name}"`;
720
+ if (!tool || !tool.run) {
721
+ // Unknown, or a run-less (client-delegated) tool that somehow reached
722
+ // server execution — both are errors here (delegated tools are handled
723
+ // above via clientTools).
724
+ content = `Error: ${tool ? `tool "${name}" has no server executor (it is client-delegated)` : `unknown tool "${name}"`}`;
712
725
  isError = true;
713
726
  }
714
727
  else {
@@ -2,3 +2,4 @@ export * from './openai.js';
2
2
  export * from './anthropic.js';
3
3
  export * from './responses.js';
4
4
  export { hasInlineToolCalls, parseInlineToolCalls } from './inlineTools.js';
5
+ export type { LLMClient, ChatMsg, StreamResult, ToolCall, ToolDef, StopReason, Usage, ContentBlockParam, } from '../types/index.js';
@@ -1,4 +1,4 @@
1
- import type { LLMClient } from '../types/index.js';
1
+ import type { ChatMsg, ContentBlockParam, LLMClient } from '../types/index.js';
2
2
  export interface OpenAIClientOptions {
3
3
  /** API key, or a function returning one per request (for round-robin key pools). */
4
4
  apiKey?: string | (() => string | undefined);
@@ -25,5 +25,28 @@ export interface OpenAIClientOptions {
25
25
  * from streamed deltas) via the returned StreamResult and the `onTool` hook.
26
26
  */
27
27
  export declare function createOpenAIClient(options?: OpenAIClientOptions): LLMClient;
28
+ /** A single message in OpenAI `/chat/completions` wire shape. */
29
+ export type OpenAIChatMessage = Record<string, unknown>;
30
+ /**
31
+ * Convert the SDK's provider-neutral `ChatMsg[]` into OpenAI `/chat/completions`
32
+ * `messages`. Exported so custom `LLMClient` authors who bring their own
33
+ * transport (proxy, encryption, alternate URL) can reuse the SDK's canonical
34
+ * wire conversion instead of forking it — keeping content-block mapping
35
+ * (text / image / PDF `document` / `tool_result`) in lockstep with the
36
+ * built-in `createOpenAIClient`.
37
+ */
38
+ export declare function toOpenAIMessages(messages: ChatMsg[]): OpenAIChatMessage[];
39
+ /** Convert our provider-neutral ChatMsg into an OpenAI chat message. */
40
+ export declare function toOpenAIMessage(msg: ChatMsg): OpenAIChatMessage;
41
+ /**
42
+ * Map content blocks to OpenAI multimodal `content` parts
43
+ * (`text` / `image_url` / `file`). Exported for custom `LLMClient` authors.
44
+ */
45
+ export declare function blocksToOpenAIContent(blocks: ContentBlockParam[]): unknown;
46
+ /**
47
+ * Flatten `text` + nested `tool_result` content blocks to a plain string
48
+ * (used for `assistant`/`tool` roles). Exported for custom `LLMClient` authors.
49
+ */
50
+ export declare function blocksToText(blocks: ContentBlockParam[]): string;
28
51
  /** Read an SSE response body, invoking `onData` for each `data:` payload. */
29
52
  export declare function consumeSSE(body: ReadableStream<Uint8Array>, onData: (data: string) => void): Promise<void>;
@@ -131,8 +131,19 @@ function mapFinishReason(reason) {
131
131
  return reason ? 'end_turn' : null;
132
132
  }
133
133
  }
134
+ /**
135
+ * Convert the SDK's provider-neutral `ChatMsg[]` into OpenAI `/chat/completions`
136
+ * `messages`. Exported so custom `LLMClient` authors who bring their own
137
+ * transport (proxy, encryption, alternate URL) can reuse the SDK's canonical
138
+ * wire conversion instead of forking it — keeping content-block mapping
139
+ * (text / image / PDF `document` / `tool_result`) in lockstep with the
140
+ * built-in `createOpenAIClient`.
141
+ */
142
+ export function toOpenAIMessages(messages) {
143
+ return messages.map(toOpenAIMessage);
144
+ }
134
145
  /** Convert our provider-neutral ChatMsg into an OpenAI chat message. */
135
- function toOpenAIMessage(msg) {
146
+ export function toOpenAIMessage(msg) {
136
147
  if (msg.role === 'tool') {
137
148
  return {
138
149
  role: 'tool',
@@ -155,7 +166,11 @@ function toOpenAIMessage(msg) {
155
166
  }
156
167
  return { role: msg.role, content: blocksToOpenAIContent(msg.content) };
157
168
  }
158
- function blocksToOpenAIContent(blocks) {
169
+ /**
170
+ * Map content blocks to OpenAI multimodal `content` parts
171
+ * (`text` / `image_url` / `file`). Exported for custom `LLMClient` authors.
172
+ */
173
+ export function blocksToOpenAIContent(blocks) {
159
174
  const parts = [];
160
175
  for (const b of blocks) {
161
176
  if (b.type === 'text')
@@ -186,7 +201,11 @@ function blocksToOpenAIContent(blocks) {
186
201
  }
187
202
  return parts.length ? parts : '';
188
203
  }
189
- function blocksToText(blocks) {
204
+ /**
205
+ * Flatten `text` + nested `tool_result` content blocks to a plain string
206
+ * (used for `assistant`/`tool` roles). Exported for custom `LLMClient` authors.
207
+ */
208
+ export function blocksToText(blocks) {
190
209
  return blocks
191
210
  .map((b) => {
192
211
  if (b.type === 'text')
package/dist/loop.d.ts CHANGED
@@ -15,6 +15,22 @@ export interface RunToolLoopOptions {
15
15
  signal?: AbortSignal;
16
16
  /** Optional permission gate; a `deny` result turns into an error tool_result. */
17
17
  canUseTool?: CanUseTool;
18
+ /** Tool names to DELEGATE to the host instead of running via `ctx` (inline). A
19
+ * tool with no `run` is auto-delegated too (Vercel-style "no execute = client"). */
20
+ clientTools?: string[];
21
+ /** Executor for delegated tools — runs the call (e.g. on a browser WebContainer)
22
+ * and returns the result inline. Required if any tool is delegated. */
23
+ onClientTool?: (req: {
24
+ tool_use_id: string;
25
+ name: string;
26
+ input: Record<string, unknown>;
27
+ }) => Promise<{
28
+ content: unknown;
29
+ is_error?: boolean;
30
+ }> | {
31
+ content: unknown;
32
+ is_error?: boolean;
33
+ };
18
34
  /** Emit `stream_event` text deltas as the assistant streams. */
19
35
  includePartialMessages?: boolean;
20
36
  /** Correlation id stamped on every emitted SDKMessage. */
package/dist/loop.js CHANGED
@@ -75,8 +75,9 @@ function createPushQueue() {
75
75
  * for await (const m of runToolLoop({ history, tools, llm, model, ctx })) render(m)
76
76
  */
77
77
  export async function* runToolLoop(opts) {
78
- const { history, llm, model, ctx, signal, canUseTool } = opts;
78
+ const { history, llm, model, ctx, signal, canUseTool, onClientTool } = opts;
79
79
  const tools = opts.tools;
80
+ const clientTools = new Set(opts.clientTools ?? []);
80
81
  const maxTurns = opts.maxTurns ?? 50;
81
82
  const sessionId = opts.sessionId ?? uuid();
82
83
  const emitPartial = !!opts.includePartialMessages;
@@ -186,7 +187,27 @@ export async function* runToolLoop(opts) {
186
187
  const tool = byName.get(name);
187
188
  let content = '';
188
189
  let isError = false;
189
- if (!tool) {
190
+ // Delegated tool (listed in clientTools, or has no `run`): execute on the
191
+ // host via onClientTool instead of `ctx` — never touches the server FS.
192
+ const delegated = clientTools.has(name) || (tool != null && !tool.run);
193
+ if (delegated) {
194
+ if (!onClientTool) {
195
+ content = `No client executor for "${name}" (delegated tool; pass onClientTool).`;
196
+ isError = true;
197
+ }
198
+ else {
199
+ try {
200
+ const r = await onClientTool({ tool_use_id: call.id, name, input });
201
+ content = (typeof r.content === 'string' ? r.content : JSON.stringify(r.content ?? ''));
202
+ isError = !!r.is_error;
203
+ }
204
+ catch (err) {
205
+ content = `Error (client) ${name}: ${err instanceof Error ? err.message : String(err)}`;
206
+ isError = true;
207
+ }
208
+ }
209
+ }
210
+ else if (!tool) {
190
211
  content = `Error: unknown tool "${name}"`;
191
212
  isError = true;
192
213
  }
package/dist/query.d.ts CHANGED
@@ -29,6 +29,10 @@ export interface QueryOptions {
29
29
  /** Tool names the HOST/client executes (e.g. bash on a browser WebContainer). The agent
30
30
  * emits a `client_tool_request` + pauses; the client runs it and resumes with results. */
31
31
  clientTools?: string[];
32
+ /** One switch: delegate ALL built-in workspace tools (bash + file ops) to the host
33
+ * so the server never runs them against its in-memory workspace — execution happens
34
+ * client-side (pair with anyclaude-react `createWorkspaceClientTools`). */
35
+ clientWorkspaceTools?: boolean;
32
36
  /** Results for client-tool calls, injected before continuing (with continueRun). */
33
37
  clientToolResults?: Array<{
34
38
  tool_use_id: string;
package/dist/query.js CHANGED
@@ -23,6 +23,7 @@ export function query(options) {
23
23
  maxDurationMs: options.maxDurationMs,
24
24
  continueRun: options.continueRun,
25
25
  clientTools: options.clientTools,
26
+ clientWorkspaceTools: options.clientWorkspaceTools,
26
27
  clientToolResults: options.clientToolResults,
27
28
  cwd: options.cwd,
28
29
  sessionId: options.sessionId,
@@ -9,8 +9,10 @@ export interface DefineToolSpec {
9
9
  properties: Record<string, unknown>;
10
10
  required?: string[];
11
11
  };
12
- /** Execution method. Receives the parsed input + the tool context (fs/exec/cwd/signal/…). */
13
- run: (input: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult> | ToolResult;
12
+ /** Execution method. Receives the parsed input + the tool context (fs/exec/cwd/signal/…).
13
+ * OMIT it to make this a CLIENT/delegated tool the agent loop emits a
14
+ * client_tool_request and the host executes it (resume with clientToolResults). */
15
+ run?: (input: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult> | ToolResult;
14
16
  /** Optional: spill threshold for large outputs (see Tool.maxResultChars). */
15
17
  maxResultChars?: number;
16
18
  }
@@ -13,8 +13,10 @@ export function defineTool(spec) {
13
13
  },
14
14
  },
15
15
  },
16
- run: async (input, ctx) => spec.run(input, ctx),
17
16
  };
17
+ // With a run → server-executed. Without → client-delegated (no run on the Tool).
18
+ if (spec.run)
19
+ tool.run = async (input, ctx) => spec.run(input, ctx);
18
20
  if (spec.maxResultChars !== undefined)
19
21
  tool.maxResultChars = spec.maxResultChars;
20
22
  return tool;
@@ -81,7 +81,10 @@ export interface ToolResult {
81
81
  /** A tool pairs an OpenAI-shape function definition with its implementation. */
82
82
  export interface Tool {
83
83
  def: ToolDef;
84
- run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult>;
84
+ /** Server-side executor. OMIT to make the tool client-delegated (Vercel-style
85
+ * "no execute = client"): the loop emits a client_tool_request instead of
86
+ * running it, and the host supplies the result. */
87
+ run?(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult>;
85
88
  /**
86
89
  * Max result size in characters before the agent loop spills the full output
87
90
  * to a file and replaces it with a preview + path (see large-output handling).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyclaude-sdk",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
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",