@deepstrike/sdk 0.1.12 → 0.1.13

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 CHANGED
@@ -88,6 +88,8 @@ for await (const event of agent.runStreaming("Summarize README.md")) {
88
88
 
89
89
  All providers accept `RetryConfig` for exponential backoff and share a `CircuitBreaker`.
90
90
 
91
+ `extensions` are forwarded by every provider in both `complete()` and `stream()` while SDK-owned structural fields such as `model`, `messages`, `tools`, and streaming flags remain protected. Provider-specific controls still keep their native spellings: for example Anthropic `thinking` / `betas`, OpenAI Responses `reasoning`, Gemini `generationConfig`, Ollama `think` / `options`, DeepSeek `thinking` + `reasoningEffort`, and Qwen `enableThinking` + `thinkingBudget`.
92
+
91
93
  OpenAI can also be selected through the provider catalog:
92
94
 
93
95
  ```typescript
@@ -108,7 +110,7 @@ const agent = new Agent(provider, {
108
110
  maxTokens: 4096, // context window size
109
111
  maxTurns: 25, // max turns (default 25)
110
112
  timeoutMs: 60_000, // timeout in ms
111
- extensions: { temperature: 0.1 }, // pass-through to LLM
113
+ extensions: { temperature: 0.1 }, // provider-native controls, passed through to the LLM
112
114
  skillDir: "./skills", // skill .md files directory
113
115
  knowledgeSource: myKS, // KnowledgeSource implementation
114
116
  signalSource: rx, // SignalSource for external signals
@@ -307,6 +309,8 @@ console.log(out.passed, out.feedback)
307
309
  | `text_delta` | `delta` |
308
310
  | `thinking_delta` | `delta` |
309
311
  | `tool_call` | `id`, `name`, `arguments` |
312
+ | `tool_delta` | `callId`, `delta?`, `chunk?` |
313
+ | `tool_suspend` | `callId`, `suspensionId`, `payload?` |
310
314
  | `tool_result` | `callId`, `content`, `isError` |
311
315
  | `permission_request` | `toolName`, `reason` |
312
316
  | `done` | `iterations`, `totalTokens`, `status` |
package/dist/agent.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { LLMProvider, StreamEvent } from "./types.js";
1
+ import type { LLMProvider, StreamEvent, ToolSuspendEvent } from "./types.js";
2
2
  import type { RegisteredTool } from "./tools/index.js";
3
3
  import type { DreamStore, DreamResult, SessionStore } from "./memory/protocols.js";
4
4
  import type { KnowledgeSource } from "./knowledge/source.js";
@@ -34,6 +34,7 @@ export interface AgentOptions {
34
34
  * `evaluate(toolName: string, argsJson: string): { kind: string; reason?: string; retryAfterMs?: number }`.
35
35
  * When provided, every tool call is evaluated through the full pipeline.
36
36
  */
37
+ onToolSuspend?: (event: ToolSuspendEvent) => Promise<unknown> | unknown;
37
38
  governance?: {
38
39
  setTime?(nowMs: bigint): void;
39
40
  evaluate(toolName: string, argsJson: string): {
@@ -71,6 +72,7 @@ export declare class Agent {
71
72
  */
72
73
  run(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): Promise<string>;
73
74
  runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): AsyncIterable<StreamEvent>;
75
+ private runRegularTool;
74
76
  private loadSession;
75
77
  private saveSession;
76
78
  private toMessage;
package/dist/agent.js CHANGED
@@ -1,4 +1,4 @@
1
- import { executeTools } from "./tools/index.js";
1
+ import { isAsyncIterable, normalizeToolChunk, toolChunkText, validateToolArguments } from "./tools/index.js";
2
2
  import { readSkillFile, scanSkillDir } from "./skills/loader.js";
3
3
  import { getKernel } from "./kernel.js";
4
4
  export class Agent {
@@ -245,7 +245,22 @@ export class Agent {
245
245
  : knowledgeCalls.map((c) => ({ callId: c.id, name: c.name, output: "Knowledge source not configured.", isError: true }));
246
246
  for (const r of [...skillResults, ...memoryResults, ...knowledgeResults])
247
247
  yield { type: "tool_result", callId: r.callId, name: r.name, content: r.output, isError: r.isError };
248
- const results = await executeTools(regularCalls, this.tools);
248
+ const results = [];
249
+ const active = regularCalls.map(call => {
250
+ const iterator = this.runRegularTool(call);
251
+ return { iterator, pending: iterator.next() };
252
+ });
253
+ while (active.length) {
254
+ const next = await Promise.race(active.map((task, index) => task.pending.then(result => ({ index, result }))));
255
+ const task = active[next.index];
256
+ if (next.result.done) {
257
+ results.push(next.result.value);
258
+ active.splice(next.index, 1);
259
+ continue;
260
+ }
261
+ yield next.result.value;
262
+ task.pending = task.iterator.next();
263
+ }
249
264
  for (const r of results) {
250
265
  const name = regularCalls.find((c) => c.id === r.callId)?.name ?? "";
251
266
  yield { type: "tool_result", callId: r.callId, name, content: r.output, isError: r.isError };
@@ -299,6 +314,60 @@ export class Agent {
299
314
  status,
300
315
  };
301
316
  }
317
+ async *runRegularTool(call) {
318
+ const registeredTool = this.tools.get(call.name);
319
+ if (!registeredTool)
320
+ return { callId: call.id, output: `unknown tool: ${call.name}`, isError: true };
321
+ try {
322
+ const args = JSON.parse(call.arguments || "{}");
323
+ const validationError = validateToolArguments(registeredTool.schema.parameters, args);
324
+ if (validationError)
325
+ return { callId: call.id, output: `invalid arguments: ${validationError}`, isError: true };
326
+ const output = await registeredTool.execute(args);
327
+ if (isAsyncIterable(output)) {
328
+ let combined = "";
329
+ const iterator = output[Symbol.asyncIterator]();
330
+ let resumeValue;
331
+ while (true) {
332
+ const next = await iterator.next(resumeValue);
333
+ resumeValue = undefined;
334
+ if (next.done)
335
+ break;
336
+ const rawChunk = next.value;
337
+ const chunk = normalizeToolChunk(rawChunk);
338
+ if (chunk.type === "suspend") {
339
+ const event = {
340
+ type: "tool_suspend",
341
+ callId: call.id,
342
+ name: call.name,
343
+ suspensionId: chunk.suspensionId,
344
+ ...(chunk.payload ? { payload: chunk.payload } : {}),
345
+ };
346
+ yield event;
347
+ if (!this.options.onToolSuspend) {
348
+ return { callId: call.id, output: `tool suspended without resume handler: ${chunk.suspensionId}`, isError: true };
349
+ }
350
+ resumeValue = await this.options.onToolSuspend(event);
351
+ continue;
352
+ }
353
+ const delta = toolChunkText(rawChunk);
354
+ combined += delta;
355
+ yield {
356
+ type: "tool_delta",
357
+ callId: call.id,
358
+ name: call.name,
359
+ ...(delta ? { delta } : {}),
360
+ ...(typeof rawChunk === "string" ? {} : { chunk }),
361
+ };
362
+ }
363
+ return { callId: call.id, output: combined, isError: false };
364
+ }
365
+ return { callId: call.id, output, isError: false };
366
+ }
367
+ catch (err) {
368
+ return { callId: call.id, output: String(err), isError: true };
369
+ }
370
+ }
302
371
  async loadSession(sessionId) {
303
372
  return this.options.sessionStore
304
373
  ? this.options.sessionStore.loadSession(sessionId)
@@ -38,6 +38,16 @@ export type HarnessEvent = {
38
38
  type: "tool_call";
39
39
  id: string;
40
40
  name: string;
41
+ } | {
42
+ type: "tool_delta";
43
+ callId: string;
44
+ delta?: string;
45
+ chunk?: Record<string, unknown>;
46
+ } | {
47
+ type: "tool_suspend";
48
+ callId: string;
49
+ suspensionId: string;
50
+ payload?: Record<string, unknown>;
41
51
  } | {
42
52
  type: "tool_result";
43
53
  callId: string;
@@ -74,6 +74,14 @@ export class HarnessLoop {
74
74
  const tc = evt;
75
75
  yield { type: "tool_call", id: tc.id, name: tc.name };
76
76
  }
77
+ else if (evt.type === "tool_delta") {
78
+ const td = evt;
79
+ yield { type: "tool_delta", callId: td.callId, ...(td.delta ? { delta: td.delta } : {}), ...(td.chunk ? { chunk: td.chunk } : {}) };
80
+ }
81
+ else if (evt.type === "tool_suspend") {
82
+ const ts = evt;
83
+ yield { type: "tool_suspend", callId: ts.callId, suspensionId: ts.suspensionId, ...(ts.payload ? { payload: ts.payload } : {}) };
84
+ }
77
85
  else if (evt.type === "tool_result") {
78
86
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
87
  const tr = evt;
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export type { OpenAIResponsesRunState } from "./providers/openai-responses.js";
15
15
  export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
16
16
  export { createProvider } from "./providers/catalog.js";
17
17
  export type { CreateProviderOptions, EndpointProfileId } from "./providers/catalog.js";
18
- export { tool, executeTools, readFile } from "./tools/index.js";
18
+ export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
19
19
  export type { RegisteredTool } from "./tools/index.js";
20
20
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
21
21
  export type { SkillMetadata } from "./skills/loader.js";
@@ -31,7 +31,7 @@ export { PermissionManager, PermissionMode } from "./safety/permissions.js";
31
31
  export type { PermissionDecision, Permission } from "./safety/permissions.js";
32
32
  export { Governance } from "./governance.js";
33
33
  export type { GovernanceVerdict } from "./governance.js";
34
- export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, RenderedContext, } from "./types.js";
34
+ export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, RenderedContext, } from "./types.js";
35
35
  export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
36
36
  export { ContractBuilder, formatContractForSystemPrompt, contractToCriteriaStrings, } from "./collaboration/contract.js";
37
37
  export { AgentPool } from "./collaboration/pool.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { OpenAIChatAdapter } from "./providers/openai-chat.js";
12
12
  export { OpenAIResponsesAdapter, OpenAIResponsesProvider } from "./providers/openai-responses.js";
13
13
  export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
14
14
  export { createProvider } from "./providers/catalog.js";
15
- export { tool, executeTools, readFile } from "./tools/index.js";
15
+ export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
16
16
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
17
17
  export { WorkingMemory } from "./memory/working.js";
18
18
  export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
@@ -15,8 +15,12 @@ export declare class AnthropicProvider implements LLMProvider {
15
15
  baseDelay: number;
16
16
  }, options?: AnthropicProviderOptions);
17
17
  private buildTools;
18
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
18
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
19
19
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
20
+ private requestExtensions;
21
+ private hasBetas;
22
+ private createMessage;
23
+ private streamMessage;
20
24
  private buildMessages;
21
25
  private rememberNativeBlocks;
22
26
  private assistantReplayKey;
@@ -1,6 +1,6 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker, normalizeToolCall, toAnthropicMessages } from "./base.js";
3
+ import { CircuitBreaker, normalizeToolCall, omitExtensionKeys, toAnthropicMessages } from "./base.js";
4
4
  export class AnthropicProvider {
5
5
  model;
6
6
  client;
@@ -25,21 +25,23 @@ export class AnthropicProvider {
25
25
  input_schema: JSON.parse(t.parameters),
26
26
  }));
27
27
  }
28
- async complete(context, tools) {
28
+ async complete(context, tools, extensions) {
29
29
  if (this.circuit.isOpen())
30
30
  throw new Error("Circuit breaker open");
31
31
  const system = context.systemText || undefined;
32
32
  const msgs = this.buildMessages(context);
33
+ const requestExtensions = this.requestExtensions(extensions);
33
34
  let lastErr;
34
35
  for (let i = 0; i < this.maxRetries; i++) {
35
36
  try {
36
- const resp = await this.client.messages.create({
37
+ const resp = await this.createMessage({
38
+ ...requestExtensions,
37
39
  model: this.model,
38
- max_tokens: 8096,
40
+ max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
39
41
  ...(system ? { system } : {}),
40
42
  messages: msgs,
41
43
  ...(tools.length ? { tools: this.buildTools(tools) } : {}),
42
- });
44
+ }, extensions);
43
45
  this.circuit.recordSuccess();
44
46
  let content = "";
45
47
  const toolCalls = [];
@@ -68,17 +70,19 @@ export class AnthropicProvider {
68
70
  async *stream(context, tools, extensions) {
69
71
  const system = context.systemText || undefined;
70
72
  const msgs = this.buildMessages(context);
73
+ const requestExtensions = this.requestExtensions(extensions);
71
74
  const toolBlocks = {};
72
75
  const nativeBlocks = {};
73
76
  let finalText = "";
74
77
  const finalToolCalls = [];
75
- const stream = this.client.messages.stream({
78
+ const stream = this.streamMessage({
79
+ ...requestExtensions,
76
80
  model: this.model,
77
- max_tokens: 8096,
81
+ max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
78
82
  ...(system ? { system } : {}),
79
83
  messages: msgs,
80
84
  ...(tools.length ? { tools: this.buildTools(tools) } : {}),
81
- });
85
+ }, extensions);
82
86
  for await (const evt of stream) {
83
87
  if (evt.type === "content_block_start") {
84
88
  nativeBlocks[evt.index] = { ...evt.content_block };
@@ -121,6 +125,23 @@ export class AnthropicProvider {
121
125
  }
122
126
  this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
123
127
  }
128
+ requestExtensions(extensions) {
129
+ return omitExtensionKeys(extensions, ["model", "messages", "system", "tools", "max_tokens", "stream"]);
130
+ }
131
+ hasBetas(extensions) {
132
+ const betas = extensions?.betas;
133
+ return Array.isArray(betas) && betas.length > 0;
134
+ }
135
+ createMessage(params, extensions) {
136
+ return this.hasBetas(extensions)
137
+ ? this.client.beta.messages.create(params)
138
+ : this.client.messages.create(params);
139
+ }
140
+ streamMessage(params, extensions) {
141
+ return (this.hasBetas(extensions)
142
+ ? this.client.beta.messages.stream(params)
143
+ : this.client.messages.stream(params));
144
+ }
124
145
  buildMessages(context) {
125
146
  return toAnthropicMessages(context.turns, message => this.nativeAssistantBlocks.get(this.assistantReplayKey(message)));
126
147
  }
@@ -9,6 +9,7 @@ export declare class CircuitBreaker {
9
9
  recordSuccess(): void;
10
10
  recordFailure(): void;
11
11
  }
12
+ export declare function omitExtensionKeys(extensions: Record<string, unknown> | undefined, keys: readonly string[]): Record<string, unknown>;
12
13
  export declare function normalizeToolCall(id: string, name: string, args: unknown): {
13
14
  id: string;
14
15
  name: string;
@@ -26,6 +26,12 @@ export class CircuitBreaker {
26
26
  this.openedAt = Date.now();
27
27
  }
28
28
  }
29
+ export function omitExtensionKeys(extensions, keys) {
30
+ if (!extensions)
31
+ return {};
32
+ const blocked = new Set(keys);
33
+ return Object.fromEntries(Object.entries(extensions).filter(([key]) => !blocked.has(key)));
34
+ }
29
35
  export function normalizeToolCall(id, name, args) {
30
36
  const n = String(name ?? "").trim();
31
37
  if (!n)
@@ -1,9 +1,10 @@
1
- import type { RenderedContext, ToolSchema, StreamEvent } from "../types.js";
1
+ import type { Message, RenderedContext, ToolSchema, StreamEvent } from "../types.js";
2
2
  import { OpenAIChatProvider } from "./openai.js";
3
3
  export declare class DeepSeekProvider extends OpenAIChatProvider {
4
4
  constructor(apiKey: string, model?: "deepseek-v4-flash" | "deepseek-v4-pro", retry?: {
5
5
  maxRetries: number;
6
6
  baseDelay: number;
7
7
  }, baseURL?: string);
8
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
8
9
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
10
  }
@@ -1,19 +1,34 @@
1
1
  import { OpenAIChatProvider } from "./openai.js";
2
2
  import { endpointProfiles } from "./profiles.js";
3
+ import { omitExtensionKeys } from "./base.js";
3
4
  const DEEPSEEK_BASE = endpointProfiles["deepseek.openai"].baseURL;
4
5
  export class DeepSeekProvider extends OpenAIChatProvider {
5
6
  constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = DEEPSEEK_BASE) {
6
7
  super(apiKey, model, retry, baseURL);
7
8
  }
9
+ async complete(context, tools, extensions) {
10
+ const thinking = extensions?.thinking === false ? "disabled" : "enabled";
11
+ const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
12
+ return super.complete(context, tools, {
13
+ ...omitExtensionKeys(extensions, ["thinking", "reasoningEffort", "exposeReasoning", "extra_body", "reasoning_effort"]),
14
+ reasoning_effort: reasoningEffort,
15
+ extra_body: { thinking: { type: thinking } },
16
+ });
17
+ }
8
18
  async *stream(context, tools, extensions) {
9
19
  const exposeReasoning = extensions?.exposeReasoning ?? false;
10
20
  const thinking = extensions?.thinking === false ? "disabled" : "enabled";
11
21
  const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
12
22
  const msgs = this.chat.buildMessages(context);
13
23
  const toolCallBufs = {};
24
+ const emittedToolCallIndexes = new Set();
14
25
  let reasoningContent = "";
15
26
  let finalText = "";
16
27
  const stream = await this.client.chat.completions.create({
28
+ ...omitExtensionKeys(extensions, [
29
+ "model", "messages", "tools", "stream", "extra_body", "reasoning_effort",
30
+ "exposeReasoning", "thinking", "reasoningEffort",
31
+ ]),
17
32
  model: this.model,
18
33
  messages: msgs,
19
34
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
@@ -48,7 +63,10 @@ export class DeepSeekProvider extends OpenAIChatProvider {
48
63
  id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
49
64
  }));
50
65
  this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
51
- for (const tb of Object.values(toolCallBufs)) {
66
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
67
+ const idx = Number(index);
68
+ if (emittedToolCallIndexes.has(idx))
69
+ continue;
52
70
  let args = {};
53
71
  try {
54
72
  args = JSON.parse(tb.argsBuf || "{}");
@@ -56,9 +74,30 @@ export class DeepSeekProvider extends OpenAIChatProvider {
56
74
  catch {
57
75
  args = {};
58
76
  }
77
+ emittedToolCallIndexes.add(idx);
59
78
  yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
60
79
  }
61
80
  }
62
81
  }
82
+ const toolCalls = Object.values(toolCallBufs).map(tb => ({
83
+ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
84
+ }));
85
+ if (toolCalls.length) {
86
+ this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
87
+ }
88
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
89
+ const idx = Number(index);
90
+ if (emittedToolCallIndexes.has(idx))
91
+ continue;
92
+ let args = {};
93
+ try {
94
+ args = JSON.parse(tb.argsBuf || "{}");
95
+ }
96
+ catch {
97
+ args = {};
98
+ }
99
+ emittedToolCallIndexes.add(idx);
100
+ yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
101
+ }
63
102
  }
64
103
  }
@@ -9,6 +9,7 @@ export declare class GeminiProvider implements LLMProvider {
9
9
  maxRetries: number;
10
10
  baseDelay: number;
11
11
  }, baseURL?: string);
12
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
12
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
13
13
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
14
+ private modelExtensions;
14
15
  }
@@ -61,7 +61,7 @@ export class GeminiProvider {
61
61
  this.maxRetries = retry.maxRetries;
62
62
  this.baseDelay = retry.baseDelay;
63
63
  }
64
- async complete(context, tools) {
64
+ async complete(context, tools, extensions) {
65
65
  if (this.circuit.isOpen())
66
66
  throw new Error("Circuit breaker open");
67
67
  const system = context.systemText || undefined;
@@ -71,6 +71,7 @@ export class GeminiProvider {
71
71
  for (let i = 0; i < this.maxRetries; i++) {
72
72
  try {
73
73
  const m = this.genAI.getGenerativeModel({
74
+ ...this.modelExtensions(extensions),
74
75
  model: this.model,
75
76
  ...(system ? { systemInstruction: system } : {}),
76
77
  ...(geminiTools.length ? { tools: geminiTools } : {}),
@@ -111,27 +112,34 @@ export class GeminiProvider {
111
112
  const contents = buildContents(context.turns);
112
113
  const geminiTools = buildTools(tools);
113
114
  const m = this.genAI.getGenerativeModel({
115
+ ...this.modelExtensions(extensions),
114
116
  model: this.model,
115
117
  ...(system ? { systemInstruction: system } : {}),
116
118
  ...(geminiTools.length ? { tools: geminiTools } : {}),
117
119
  });
118
120
  const result = await m.generateContentStream({ contents });
119
- const toolCallBufs = {};
121
+ const toolCalls = [];
120
122
  for await (const chunk of result.stream) {
121
123
  for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
122
124
  if (part.text)
123
125
  yield { type: "text_delta", delta: part.text };
124
126
  else if (part.functionCall) {
125
127
  const { name, args } = part.functionCall;
126
- toolCallBufs[name] = { name, args: args };
128
+ toolCalls.push({ id: `call_${toolCalls.length + 1}`, name, args: args });
127
129
  }
128
130
  }
129
131
  }
130
- for (const [id, tc] of Object.entries(toolCallBufs)) {
131
- yield { type: "tool_call", id, name: tc.name, arguments: tc.args };
132
+ for (const tc of toolCalls) {
133
+ yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.args };
132
134
  }
133
135
  const usage = (await result.response).usageMetadata;
134
136
  if (usage?.totalTokenCount)
135
137
  yield { type: "usage", totalTokens: usage.totalTokenCount };
136
138
  }
139
+ modelExtensions(extensions) {
140
+ if (!extensions)
141
+ return {};
142
+ const { model: _model, systemInstruction: _systemInstruction, tools: _tools, ...rest } = extensions;
143
+ return rest;
144
+ }
137
145
  }
@@ -4,6 +4,8 @@ export declare class OllamaProvider implements LLMProvider {
4
4
  private readonly baseUrl;
5
5
  constructor(model?: string, baseUrl?: string);
6
6
  private toOllamaMessages;
7
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
7
+ private buildTools;
8
+ private requestExtensions;
9
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
8
10
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
11
  }
@@ -1,4 +1,4 @@
1
- import { normalizeToolCall } from "./base.js";
1
+ import { normalizeToolCall, omitExtensionKeys } from "./base.js";
2
2
  export class OllamaProvider {
3
3
  model;
4
4
  baseUrl;
@@ -22,11 +22,26 @@ export class OllamaProvider {
22
22
  }
23
23
  return result;
24
24
  }
25
- async complete(context, tools) {
25
+ buildTools(tools) {
26
+ return tools.map(t => ({
27
+ type: "function",
28
+ function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
29
+ }));
30
+ }
31
+ requestExtensions(extensions) {
32
+ return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream"]);
33
+ }
34
+ async complete(context, tools, extensions) {
26
35
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
27
36
  method: "POST",
28
37
  headers: { "Content-Type": "application/json" },
29
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(context), stream: false }),
38
+ body: JSON.stringify({
39
+ ...this.requestExtensions(extensions),
40
+ model: this.model,
41
+ messages: this.toOllamaMessages(context),
42
+ ...(tools.length ? { tools: this.buildTools(tools) } : {}),
43
+ stream: false,
44
+ }),
30
45
  });
31
46
  if (!resp.ok)
32
47
  throw new Error(`Ollama error: ${resp.status}`);
@@ -37,13 +52,20 @@ export class OllamaProvider {
37
52
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
38
53
  method: "POST",
39
54
  headers: { "Content-Type": "application/json" },
40
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(context), stream: true }),
55
+ body: JSON.stringify({
56
+ ...this.requestExtensions(extensions),
57
+ model: this.model,
58
+ messages: this.toOllamaMessages(context),
59
+ ...(tools.length ? { tools: this.buildTools(tools) } : {}),
60
+ stream: true,
61
+ }),
41
62
  });
42
63
  if (!resp.ok)
43
64
  throw new Error(`Ollama error: ${resp.status}`);
44
65
  const reader = resp.body.getReader();
45
66
  const decoder = new TextDecoder();
46
67
  let buf = "";
68
+ const pendingToolCalls = new Map();
47
69
  while (true) {
48
70
  const { done, value } = await reader.read();
49
71
  if (done)
@@ -59,13 +81,25 @@ export class OllamaProvider {
59
81
  if (chunk.message?.content)
60
82
  yield { type: "text_delta", delta: chunk.message.content };
61
83
  for (const tc of chunk.message?.tool_calls ?? []) {
62
- const norm = normalizeToolCall(crypto.randomUUID(), tc.function.name, tc.function.arguments);
63
- if (norm)
64
- yield { type: "tool_call", id: norm.id, name: norm.name, arguments: JSON.parse(norm.arguments) };
84
+ const norm = normalizeToolCall("", tc.function.name, tc.function.arguments);
85
+ if (!norm)
86
+ continue;
87
+ const args = JSON.parse(norm.arguments);
88
+ const key = `${norm.name}:${norm.arguments}`;
89
+ if (!pendingToolCalls.has(key)) {
90
+ pendingToolCalls.set(key, {
91
+ id: `call_${pendingToolCalls.size + 1}`,
92
+ name: norm.name,
93
+ arguments: args,
94
+ });
95
+ }
65
96
  }
66
97
  }
67
98
  catch { /* skip malformed lines */ }
68
99
  }
69
100
  }
101
+ for (const tc of pendingToolCalls.values()) {
102
+ yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments };
103
+ }
70
104
  }
71
105
  }
@@ -36,7 +36,8 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
36
36
  baseDelay: number;
37
37
  }, baseURL?: string);
38
38
  createRunState(): OpenAIResponsesRunState;
39
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
40
- stream(context: RenderedContext, tools: ToolSchema[], _extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
39
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
40
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
41
+ private requestExtensions;
41
42
  private asRunState;
42
43
  }
@@ -1,6 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker } from "./base.js";
3
+ import { CircuitBreaker, omitExtensionKeys } from "./base.js";
4
4
  import { normalizeToolCall } from "./base.js";
5
5
  export class OpenAIResponsesAdapter {
6
6
  buildTools(tools) {
@@ -116,7 +116,7 @@ export class OpenAIResponsesProvider {
116
116
  createRunState() {
117
117
  return { coveredMessageCount: 0 };
118
118
  }
119
- async complete(context, tools) {
119
+ async complete(context, tools, extensions) {
120
120
  if (this.circuit.isOpen())
121
121
  throw new Error("Circuit breaker open");
122
122
  let lastErr;
@@ -124,6 +124,7 @@ export class OpenAIResponsesProvider {
124
124
  try {
125
125
  const instructions = this.responses.buildInstructions(context);
126
126
  const resp = await this.client.responses.create({
127
+ ...this.requestExtensions(extensions),
127
128
  model: this.model,
128
129
  input: this.responses.buildInput(context),
129
130
  ...(instructions ? { instructions } : {}),
@@ -147,11 +148,12 @@ export class OpenAIResponsesProvider {
147
148
  }
148
149
  throw lastErr;
149
150
  }
150
- async *stream(context, tools, _extensions, state) {
151
+ async *stream(context, tools, extensions, state) {
151
152
  const runState = this.asRunState(state);
152
153
  const functionCalls = new Map();
153
154
  const instructions = this.responses.buildInstructions(context);
154
155
  const stream = await this.client.responses.create({
156
+ ...this.requestExtensions(extensions),
155
157
  model: this.model,
156
158
  input: this.responses.buildInput(context, runState),
157
159
  ...(instructions ? { instructions } : {}),
@@ -204,6 +206,11 @@ export class OpenAIResponsesProvider {
204
206
  }
205
207
  }
206
208
  }
209
+ requestExtensions(extensions) {
210
+ return omitExtensionKeys(extensions, [
211
+ "model", "input", "instructions", "tools", "stream", "previous_response_id",
212
+ ]);
213
+ }
207
214
  asRunState(state) {
208
215
  if (!state)
209
216
  return this.createRunState();
@@ -13,7 +13,8 @@ export declare class OpenAIChatProvider implements LLMProvider {
13
13
  maxRetries: number;
14
14
  baseDelay: number;
15
15
  }, baseURL?: string);
16
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
16
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
17
17
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
+ protected requestExtensions(extensions?: Record<string, unknown>): Record<string, unknown>;
18
19
  }
19
20
  export { OpenAIChatProvider as OpenAIProvider };
@@ -1,6 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker } from "./base.js";
3
+ import { CircuitBreaker, omitExtensionKeys } from "./base.js";
4
4
  import { OpenAIChatAdapter } from "./openai-chat.js";
5
5
  export class OpenAIChatProvider {
6
6
  model;
@@ -16,7 +16,7 @@ export class OpenAIChatProvider {
16
16
  this.maxRetries = retry.maxRetries;
17
17
  this.baseDelay = retry.baseDelay;
18
18
  }
19
- async complete(context, tools) {
19
+ async complete(context, tools, extensions) {
20
20
  if (this.circuit.isOpen())
21
21
  throw new Error("Circuit breaker open");
22
22
  const msgs = this.chat.buildMessages(context);
@@ -24,6 +24,7 @@ export class OpenAIChatProvider {
24
24
  for (let i = 0; i < this.maxRetries; i++) {
25
25
  try {
26
26
  const resp = await this.client.chat.completions.create({
27
+ ...this.requestExtensions(extensions),
27
28
  model: this.model,
28
29
  messages: msgs,
29
30
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
@@ -45,7 +46,9 @@ export class OpenAIChatProvider {
45
46
  async *stream(context, tools, extensions) {
46
47
  const msgs = this.chat.buildMessages(context);
47
48
  const toolCallBufs = {};
49
+ const emittedToolCallIndexes = new Set();
48
50
  const stream = await this.client.chat.completions.create({
51
+ ...this.requestExtensions(extensions),
49
52
  model: this.model,
50
53
  messages: msgs,
51
54
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
@@ -73,7 +76,10 @@ export class OpenAIChatProvider {
73
76
  toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
74
77
  }
75
78
  if (choice.finish_reason === "tool_calls") {
76
- for (const tb of Object.values(toolCallBufs)) {
79
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
80
+ const idx = Number(index);
81
+ if (emittedToolCallIndexes.has(idx))
82
+ continue;
77
83
  let args = {};
78
84
  try {
79
85
  args = JSON.parse(tb.argsBuf || "{}");
@@ -81,12 +87,30 @@ export class OpenAIChatProvider {
81
87
  catch {
82
88
  args = {};
83
89
  }
90
+ emittedToolCallIndexes.add(idx);
84
91
  yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
85
92
  }
86
93
  }
87
94
  }
95
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
96
+ const idx = Number(index);
97
+ if (emittedToolCallIndexes.has(idx))
98
+ continue;
99
+ let args = {};
100
+ try {
101
+ args = JSON.parse(tb.argsBuf || "{}");
102
+ }
103
+ catch {
104
+ args = {};
105
+ }
106
+ emittedToolCallIndexes.add(idx);
107
+ yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
108
+ }
88
109
  if (totalTokens > 0)
89
110
  yield { type: "usage", totalTokens };
90
111
  }
112
+ requestExtensions(extensions) {
113
+ return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream", "stream_options"]);
114
+ }
91
115
  }
92
116
  export { OpenAIChatProvider as OpenAIProvider };
@@ -13,6 +13,8 @@ export declare class QwenProvider implements LLMProvider {
13
13
  maxRetries: number;
14
14
  baseDelay: number;
15
15
  }, baseURL?: string);
16
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
16
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
17
17
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
+ private thinkingExtraBody;
19
+ private requestExtensions;
18
20
  }
@@ -1,6 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker } from "./base.js";
3
+ import { CircuitBreaker, omitExtensionKeys } from "./base.js";
4
4
  import { OpenAIChatAdapter } from "./openai-chat.js";
5
5
  import { endpointProfiles } from "./profiles.js";
6
6
  const QWEN_BASE = endpointProfiles["qwen.dashscope"].baseURL;
@@ -18,17 +18,20 @@ export class QwenProvider {
18
18
  this.maxRetries = retry.maxRetries;
19
19
  this.baseDelay = retry.baseDelay;
20
20
  }
21
- async complete(context, tools) {
21
+ async complete(context, tools, extensions) {
22
22
  if (this.circuit.isOpen())
23
23
  throw new Error("Circuit breaker open");
24
24
  const msgs = this.chat.buildMessages(context);
25
+ const extraBody = this.thinkingExtraBody(extensions);
25
26
  let lastErr;
26
27
  for (let i = 0; i < this.maxRetries; i++) {
27
28
  try {
28
29
  const resp = await this.client.chat.completions.create({
30
+ ...this.requestExtensions(extensions),
29
31
  model: this.model,
30
32
  messages: msgs,
31
33
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
34
+ ...(extraBody ? { extra_body: extraBody } : {}),
32
35
  });
33
36
  this.circuit.recordSuccess();
34
37
  const choice = resp.choices[0].message;
@@ -45,22 +48,18 @@ export class QwenProvider {
45
48
  throw lastErr;
46
49
  }
47
50
  async *stream(context, tools, extensions) {
48
- const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
49
- const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
50
51
  const msgs = this.chat.buildMessages(context);
51
52
  const toolCallBufs = {};
53
+ const emittedToolCallIndexes = new Set();
54
+ const extraBody = this.thinkingExtraBody(extensions);
52
55
  const stream = await this.client.chat.completions.create({
56
+ ...this.requestExtensions(extensions),
53
57
  model: this.model,
54
58
  messages: msgs,
55
59
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
56
60
  stream: true,
57
61
  stream_options: { include_usage: true },
58
- ...(enableThinking ? {
59
- extra_body: {
60
- enable_thinking: true,
61
- ...(typeof thinkingBudget === "number" ? { thinking_budget: thinkingBudget } : {}),
62
- },
63
- } : {}),
62
+ ...(extraBody ? { extra_body: extraBody } : {}),
64
63
  });
65
64
  let totalTokens = 0;
66
65
  for await (const chunk of stream) {
@@ -85,7 +84,10 @@ export class QwenProvider {
85
84
  toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
86
85
  }
87
86
  if (choice.finish_reason === "tool_calls") {
88
- for (const tb of Object.values(toolCallBufs)) {
87
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
88
+ const idx = Number(index);
89
+ if (emittedToolCallIndexes.has(idx))
90
+ continue;
89
91
  let args = {};
90
92
  try {
91
93
  args = JSON.parse(tb.argsBuf || "{}");
@@ -93,11 +95,42 @@ export class QwenProvider {
93
95
  catch {
94
96
  args = {};
95
97
  }
98
+ emittedToolCallIndexes.add(idx);
96
99
  yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
97
100
  }
98
101
  }
99
102
  }
103
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
104
+ const idx = Number(index);
105
+ if (emittedToolCallIndexes.has(idx))
106
+ continue;
107
+ let args = {};
108
+ try {
109
+ args = JSON.parse(tb.argsBuf || "{}");
110
+ }
111
+ catch {
112
+ args = {};
113
+ }
114
+ emittedToolCallIndexes.add(idx);
115
+ yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
116
+ }
100
117
  if (totalTokens > 0)
101
118
  yield { type: "usage", totalTokens };
102
119
  }
120
+ thinkingExtraBody(extensions) {
121
+ const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
122
+ const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
123
+ if (!enableThinking)
124
+ return undefined;
125
+ return {
126
+ enable_thinking: true,
127
+ ...(typeof thinkingBudget === "number" ? { thinking_budget: thinkingBudget } : {}),
128
+ };
129
+ }
130
+ requestExtensions(extensions) {
131
+ return omitExtensionKeys(extensions, [
132
+ "model", "messages", "tools", "stream", "stream_options", "extra_body",
133
+ "enableThinking", "enable_thinking", "thinkingBudget", "thinking_budget",
134
+ ]);
135
+ }
103
136
  }
@@ -1,9 +1,14 @@
1
- import type { ToolSchema, ToolResult } from "../types.js";
1
+ import type { ToolChunk, ToolSchema, ToolResult } from "../types.js";
2
2
  export interface RegisteredTool {
3
3
  schema: ToolSchema;
4
- execute(args: Record<string, unknown>): Promise<string>;
4
+ execute(args: Record<string, unknown>): Promise<string> | AsyncIterable<ToolChunk>;
5
5
  }
6
6
  export declare function tool(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>) => Promise<string> | string): RegisteredTool;
7
+ export declare function streamingTool(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>) => AsyncIterable<ToolChunk>): RegisteredTool;
8
+ export declare function isAsyncIterable<T>(value: unknown): value is AsyncIterable<T>;
9
+ export declare function normalizeToolChunk(chunk: ToolChunk): Exclude<ToolChunk, string>;
10
+ export declare function toolChunkText(chunk: ToolChunk): string;
11
+ export declare function validateToolArguments(schemaJson: string, args: Record<string, unknown>): string | undefined;
7
12
  export declare function executeTools(calls: {
8
13
  id: string;
9
14
  name: string;
@@ -4,6 +4,80 @@ export function tool(name, description, parameters, fn) {
4
4
  async execute(args) { return fn(args); },
5
5
  };
6
6
  }
7
+ export function streamingTool(name, description, parameters, fn) {
8
+ return {
9
+ schema: { name, description, parameters: JSON.stringify(parameters) },
10
+ execute(args) { return fn(args); },
11
+ };
12
+ }
13
+ export function isAsyncIterable(value) {
14
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
15
+ }
16
+ export function normalizeToolChunk(chunk) {
17
+ return typeof chunk === "string" ? { type: "text", text: chunk } : chunk;
18
+ }
19
+ export function toolChunkText(chunk) {
20
+ const normalized = normalizeToolChunk(chunk);
21
+ return normalized.type === "text" ? normalized.text : "";
22
+ }
23
+ export function validateToolArguments(schemaJson, args) {
24
+ let schema;
25
+ try {
26
+ schema = JSON.parse(schemaJson);
27
+ }
28
+ catch {
29
+ return "invalid tool schema";
30
+ }
31
+ return validateValue(schema, args, "$", true);
32
+ }
33
+ function validateValue(schema, value, path, isRoot = false) {
34
+ const expectedType = schema.type;
35
+ if (typeof expectedType === "string") {
36
+ if (expectedType === "object") {
37
+ if (!value || typeof value !== "object" || Array.isArray(value))
38
+ return `${path} must be object`;
39
+ const obj = value;
40
+ for (const required of schema.required ?? []) {
41
+ if (!(required in obj))
42
+ return `${path}.${required} is required`;
43
+ }
44
+ const properties = schema.properties ?? {};
45
+ for (const [key, child] of Object.entries(properties)) {
46
+ if (key in obj) {
47
+ const err = validateValue(child, obj[key], `${path}.${key}`);
48
+ if (err)
49
+ return err;
50
+ }
51
+ }
52
+ }
53
+ else if (expectedType === "array") {
54
+ if (!Array.isArray(value))
55
+ return `${path} must be array`;
56
+ }
57
+ else if (expectedType === "string") {
58
+ if (typeof value !== "string")
59
+ return `${path} must be string`;
60
+ }
61
+ else if (expectedType === "number") {
62
+ if (typeof value !== "number" || Number.isNaN(value))
63
+ return `${path} must be number`;
64
+ }
65
+ else if (expectedType === "integer") {
66
+ if (!Number.isInteger(value))
67
+ return `${path} must be integer`;
68
+ }
69
+ else if (expectedType === "boolean") {
70
+ if (typeof value !== "boolean")
71
+ return `${path} must be boolean`;
72
+ }
73
+ }
74
+ else if (isRoot && (!value || typeof value !== "object" || Array.isArray(value))) {
75
+ return `${path} must be object`;
76
+ }
77
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value))
78
+ return `${path} must be one of enum values`;
79
+ return undefined;
80
+ }
7
81
  export async function executeTools(calls, registry) {
8
82
  return Promise.all(calls.map(async (c) => {
9
83
  const t = registry.get(c.name);
@@ -11,7 +85,16 @@ export async function executeTools(calls, registry) {
11
85
  return { callId: c.id, output: `unknown tool: ${c.name}`, isError: true };
12
86
  try {
13
87
  const args = JSON.parse(c.arguments || "{}");
88
+ const validationError = validateToolArguments(t.schema.parameters, args);
89
+ if (validationError)
90
+ return { callId: c.id, output: `invalid arguments: ${validationError}`, isError: true };
14
91
  const output = await t.execute(args);
92
+ if (isAsyncIterable(output)) {
93
+ let combined = "";
94
+ for await (const chunk of output)
95
+ combined += toolChunkText(chunk);
96
+ return { callId: c.id, output: combined, isError: false };
97
+ }
15
98
  return { callId: c.id, output, isError: false };
16
99
  }
17
100
  catch (err) {
package/dist/types.d.ts CHANGED
@@ -69,6 +69,41 @@ export interface ToolCallEvent extends StreamEvent {
69
69
  name: string;
70
70
  arguments: Record<string, unknown>;
71
71
  }
72
+ export type ToolChunk = string | {
73
+ type: "text";
74
+ text: string;
75
+ } | {
76
+ type: "progress";
77
+ progress: number;
78
+ message?: string;
79
+ } | {
80
+ type: "artifact";
81
+ artifactId: string;
82
+ mimeType?: string;
83
+ label?: string;
84
+ } | {
85
+ type: "json_patch";
86
+ patch: Record<string, unknown>;
87
+ } | {
88
+ type: "suspend";
89
+ suspensionId: string;
90
+ payload?: Record<string, unknown>;
91
+ };
92
+ export interface ToolDeltaEvent extends StreamEvent {
93
+ type: "tool_delta";
94
+ callId: string;
95
+ name: string;
96
+ /** Backward-compatible text projection when the chunk carries text. */
97
+ delta?: string;
98
+ chunk: Exclude<ToolChunk, string>;
99
+ }
100
+ export interface ToolSuspendEvent extends StreamEvent {
101
+ type: "tool_suspend";
102
+ callId: string;
103
+ name: string;
104
+ suspensionId: string;
105
+ payload?: Record<string, unknown>;
106
+ }
72
107
  export interface ToolResultEvent extends StreamEvent {
73
108
  type: "tool_result";
74
109
  callId: string;
@@ -129,6 +164,6 @@ export interface RenderedContext {
129
164
  }
130
165
  export interface LLMProvider {
131
166
  createRunState?(): ProviderRunState;
132
- complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
167
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
133
168
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
134
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "DeepStrike Node.js SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@anthropic-ai/sdk": "^0.39.0",
19
- "@deepstrike/core": "0.1.12",
19
+ "@deepstrike/core": "0.1.13",
20
20
  "@google/generative-ai": "^0.24.1",
21
21
  "openai": "^4.77.0"
22
22
  },