@plumbus/core 0.6.10 → 0.6.14

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/ai/ai/provider.d.ts +79 -6
  5. package/dist/ai/ai/refusal.d.ts +14 -0
  6. package/dist/ai/ai-service.d.ts +2 -1
  7. package/dist/ai/ai-service.d.ts.map +1 -1
  8. package/dist/ai/ai-service.js +92 -16
  9. package/dist/ai/ai-service.js.map +1 -1
  10. package/dist/ai/index.d.ts +3 -2
  11. package/dist/ai/index.d.ts.map +1 -1
  12. package/dist/ai/index.js +4 -2
  13. package/dist/ai/index.js.map +1 -1
  14. package/dist/ai/provider.d.ts +79 -6
  15. package/dist/ai/provider.d.ts.map +1 -1
  16. package/dist/ai/provider.js +279 -33
  17. package/dist/ai/provider.js.map +1 -1
  18. package/dist/ai/refusal.d.ts +14 -0
  19. package/dist/ai/refusal.d.ts.map +1 -1
  20. package/dist/ai/refusal.js +15 -0
  21. package/dist/ai/refusal.js.map +1 -1
  22. package/dist/ai/tool-loop.d.ts +58 -0
  23. package/dist/ai/tool-loop.d.ts.map +1 -0
  24. package/dist/ai/tool-loop.js +212 -0
  25. package/dist/ai/tool-loop.js.map +1 -0
  26. package/dist/ai/types/entity.d.ts +6 -1
  27. package/dist/cli/commands/agent.js +1 -1
  28. package/dist/cli/commands/agent.js.map +1 -1
  29. package/dist/cli/commands/browser-extension.d.ts.map +1 -1
  30. package/dist/cli/commands/browser-extension.js +6 -1
  31. package/dist/cli/commands/browser-extension.js.map +1 -1
  32. package/dist/cli/commands/dev.d.ts.map +1 -1
  33. package/dist/cli/commands/dev.js +14 -0
  34. package/dist/cli/commands/dev.js.map +1 -1
  35. package/dist/cli/commands/doctor.d.ts.map +1 -1
  36. package/dist/cli/commands/doctor.js +5 -4
  37. package/dist/cli/commands/doctor.js.map +1 -1
  38. package/dist/cli/commands/init.d.ts +8 -3
  39. package/dist/cli/commands/init.d.ts.map +1 -1
  40. package/dist/cli/commands/init.js +141 -16
  41. package/dist/cli/commands/init.js.map +1 -1
  42. package/dist/cli/commands/start.d.ts.map +1 -1
  43. package/dist/cli/commands/start.js +14 -0
  44. package/dist/cli/commands/start.js.map +1 -1
  45. package/dist/cli/commands/voice.d.ts.map +1 -1
  46. package/dist/cli/commands/voice.js +18 -3
  47. package/dist/cli/commands/voice.js.map +1 -1
  48. package/dist/cli/commands/worker.d.ts.map +1 -1
  49. package/dist/cli/commands/worker.js +14 -0
  50. package/dist/cli/commands/worker.js.map +1 -1
  51. package/dist/cli/index.d.ts +1 -1
  52. package/dist/cli/index.d.ts.map +1 -1
  53. package/dist/cli/index.js +1 -1
  54. package/dist/cli/index.js.map +1 -1
  55. package/dist/cli/mcp-serve-context.js +1 -1
  56. package/dist/cli/mcp-serve-context.js.map +1 -1
  57. package/dist/cli/voice-agent-bootstrap.d.ts +2 -0
  58. package/dist/cli/voice-agent-bootstrap.d.ts.map +1 -1
  59. package/dist/cli/voice-agent-bootstrap.js +1 -0
  60. package/dist/cli/voice-agent-bootstrap.js.map +1 -1
  61. package/dist/cli/voice-serve-context.d.ts +1 -0
  62. package/dist/cli/voice-serve-context.d.ts.map +1 -1
  63. package/dist/cli/voice-serve-context.js +19 -2
  64. package/dist/cli/voice-serve-context.js.map +1 -1
  65. package/dist/data/repository.d.ts.map +1 -1
  66. package/dist/data/repository.js +34 -0
  67. package/dist/data/repository.js.map +1 -1
  68. package/dist/data/schema-generator.d.ts.map +1 -1
  69. package/dist/data/schema-generator.js +8 -4
  70. package/dist/data/schema-generator.js.map +1 -1
  71. package/dist/define/defineEntity.d.ts +2 -2
  72. package/dist/define/defineEntity.d.ts.map +1 -1
  73. package/dist/define/defineEntity.js +2 -1
  74. package/dist/define/defineEntity.js.map +1 -1
  75. package/dist/errors-entry.d.ts +14 -0
  76. package/dist/errors-entry.d.ts.map +1 -0
  77. package/dist/errors-entry.js +13 -0
  78. package/dist/errors-entry.js.map +1 -0
  79. package/dist/execution/capability-executor.d.ts.map +1 -1
  80. package/dist/execution/capability-executor.js +3 -1
  81. package/dist/execution/capability-executor.js.map +1 -1
  82. package/dist/execution/capability-invocation.d.ts +3 -1
  83. package/dist/execution/capability-invocation.d.ts.map +1 -1
  84. package/dist/execution/capability-invocation.js +3 -1
  85. package/dist/execution/capability-invocation.js.map +1 -1
  86. package/dist/flows/engine.d.ts.map +1 -1
  87. package/dist/flows/engine.js +51 -13
  88. package/dist/flows/engine.js.map +1 -1
  89. package/dist/flows/flow-service.d.ts +6 -1
  90. package/dist/flows/flow-service.d.ts.map +1 -1
  91. package/dist/flows/flow-service.js +39 -1
  92. package/dist/flows/flow-service.js.map +1 -1
  93. package/dist/index.d.ts +3 -3
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +2 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/runtime/register-consumers.d.ts +3 -0
  98. package/dist/runtime/register-consumers.d.ts.map +1 -1
  99. package/dist/runtime/register-consumers.js +3 -1
  100. package/dist/runtime/register-consumers.js.map +1 -1
  101. package/dist/server/bootstrap.d.ts +1 -0
  102. package/dist/server/bootstrap.d.ts.map +1 -1
  103. package/dist/server/bootstrap.js +4 -4
  104. package/dist/server/bootstrap.js.map +1 -1
  105. package/dist/testing/context.d.ts +8 -2
  106. package/dist/testing/context.d.ts.map +1 -1
  107. package/dist/testing/context.js +35 -4
  108. package/dist/testing/context.js.map +1 -1
  109. package/dist/types/context.d.ts +107 -17
  110. package/dist/types/context.d.ts.map +1 -1
  111. package/dist/types/entity.d.ts +6 -1
  112. package/dist/types/entity.d.ts.map +1 -1
  113. package/dist/types/index.d.ts +2 -2
  114. package/dist/types/index.d.ts.map +1 -1
  115. package/dist/types/index.js.map +1 -1
  116. package/dist/types/prompt.d.ts +9 -0
  117. package/dist/types/prompt.d.ts.map +1 -1
  118. package/dist/worker/bootstrap.js +2 -2
  119. package/dist/worker/bootstrap.js.map +1 -1
  120. package/instructions/README.md +1 -1
  121. package/instructions/ai.md +61 -1
  122. package/instructions/capabilities.md +1 -1
  123. package/instructions/cli.md +1 -0
  124. package/instructions/peer-dependencies.md +17 -2
  125. package/instructions/prompts.md +4 -1
  126. package/package.json +7 -3
@@ -1,15 +1,63 @@
1
1
  import type { AIProviderConfig } from '../types/config.js';
2
2
  import { type Kind } from './model-pricing.js';
3
+ export interface AITool {
4
+ name: string;
5
+ description: string;
6
+ /** JSON Schema object root (from zodToProviderJsonSchema(...).schema). */
7
+ parameters: Record<string, unknown>;
8
+ }
9
+ export type AIToolChoice = 'auto' | 'none' | {
10
+ type: 'function';
11
+ function: {
12
+ name: string;
13
+ };
14
+ };
15
+ /** Discriminated on argumentsStatus. Adapters parse at the boundary (§6.4). */
16
+ export type AIToolCall = {
17
+ id: string;
18
+ name: string;
19
+ argumentsStatus: 'parsed';
20
+ arguments: unknown;
21
+ } | {
22
+ id: string;
23
+ name: string;
24
+ argumentsStatus: 'invalid';
25
+ /** Internal transcript data — MUST NOT reach browser events, audit, or user errors. Bounded. */
26
+ rawArguments: string;
27
+ /** Internal transcript data — MUST NOT reach browser events, audit, or user errors. Bounded. */
28
+ parseError: string;
29
+ };
30
+ export interface AIToolExecutionOptions {
31
+ /** Chat tool rounds MUST set this false (§6.2). */
32
+ parallelToolCalls?: boolean;
33
+ }
34
+ export interface AIProviderCapabilities {
35
+ tools: boolean;
36
+ streamingTools: boolean;
37
+ parallelToolCalls: boolean;
38
+ parallelToolCallControl: boolean;
39
+ namedToolChoice: boolean;
40
+ }
3
41
  /**
4
42
  * A single chat-history turn. Used for native multi-turn requests where the
5
- * model should see real conversation flow (assistant + user alternation).
6
- * Optional — when omitted, providers fall back to single-user-message mode
7
- * built from `ProviderRequest.prompt`.
43
+ * model should see real conversation flow. Optional — when omitted, providers
44
+ * fall back to single-user-message mode built from `ProviderRequest.prompt`.
45
+ * The user/assistant variants remain construction-compatible with existing
46
+ * callers; the `tool` variant carries a tool observation back to the model.
8
47
  */
9
- export interface ChatMessage {
10
- role: 'user' | 'assistant';
48
+ export type ChatMessage = {
49
+ role: 'user';
11
50
  content: string;
12
- }
51
+ } | {
52
+ role: 'assistant';
53
+ content: string;
54
+ toolCalls?: AIToolCall[];
55
+ } | {
56
+ role: 'tool';
57
+ content: string;
58
+ toolCallId: string;
59
+ name: string;
60
+ };
13
61
  export interface ProviderRequest {
14
62
  /** System prompt / instructions */
15
63
  system?: string;
@@ -34,6 +82,12 @@ export interface ProviderRequest {
34
82
  temperature?: number;
35
83
  /** Max tokens for response */
36
84
  maxTokens?: number;
85
+ /**
86
+ * Reasoning effort for models that support it. Sent ONLY when set (OpenAI
87
+ * `reasoning_effort` on o-series / gpt-5 family); adapters for providers
88
+ * without an equivalent parameter ignore it.
89
+ */
90
+ reasoningEffort?: 'low' | 'medium' | 'high';
37
91
  /** Response format hint */
38
92
  responseFormat?: 'text' | 'json';
39
93
  /** Provider-compatible JSON Schema for strict structured outputs. */
@@ -57,6 +111,12 @@ export interface ProviderRequest {
57
111
  * by providers that do not support it.
58
112
  */
59
113
  seed?: number;
114
+ /** Provider-native caller tools. When present the adapter forwards them and returns tool calls. */
115
+ tools?: AITool[];
116
+ /** Tool-choice directive forwarded to the provider. */
117
+ toolChoice?: AIToolChoice;
118
+ /** Tool-execution options (e.g. disable parallel tool calls). */
119
+ toolExecution?: AIToolExecutionOptions;
60
120
  }
61
121
  /** Compose a request's optional user signal with a timeout, returning a single AbortSignal. */
62
122
  export declare function combineRequestSignals(userSignal: AbortSignal | undefined, timeoutMs: number): AbortSignal;
@@ -65,6 +125,8 @@ export interface ProviderResponse {
65
125
  model: string;
66
126
  usage: TokenUsage;
67
127
  finishReason: string;
128
+ /** Parsed provider tool calls (present when finishReason is 'tool_calls'/'tool_use'). */
129
+ toolCalls?: AIToolCall[];
68
130
  }
69
131
  export interface TokenUsage {
70
132
  inputTokens: number;
@@ -83,6 +145,8 @@ export interface ProviderStreamEvent {
83
145
  usage?: TokenUsage;
84
146
  /** Finish reason (for done events) */
85
147
  finishReason?: string;
148
+ /** Provider tool calls on a done event (streaming tools; unused by built-in adapters this release). */
149
+ toolCalls?: AIToolCall[];
86
150
  /** Error message (for error events) */
87
151
  error?: string;
88
152
  }
@@ -142,6 +206,13 @@ export interface ListModelsFilter {
142
206
  }
143
207
  export interface AIProviderAdapter {
144
208
  readonly name: string;
209
+ /**
210
+ * Declared provider capabilities. Optional for backward compat with external
211
+ * adapters: an adapter that omits this MUST be treated by callers as
212
+ * declaring every value `false` (§6.2). Both built-in adapters declare it and
213
+ * implement caller tools natively (C11).
214
+ */
215
+ readonly capabilities?: AIProviderCapabilities;
145
216
  /** Send a completion request */
146
217
  complete(request: ProviderRequest): Promise<ProviderResponse>;
147
218
  /** Stream a completion request, yielding incremental events */
@@ -193,6 +264,8 @@ export declare class ProviderAPIError extends Error {
193
264
  });
194
265
  }
195
266
  export declare function isProviderAPIError(value: unknown): value is ProviderAPIError;
267
+ /** Normalize a provider-specific finish/stop reason into the framework's vocabulary. */
268
+ export declare function normalizeFinishReason(raw: string | undefined): 'stop' | 'length' | 'refusal' | 'tool_calls' | 'other';
196
269
  export interface OpenAIAdapterConfig {
197
270
  apiKey: string;
198
271
  model?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  // ── AI Provider Adapter Interface ──
2
2
  // Abstract interface for AI provider adapters (OpenAI, Anthropic, etc.)
3
3
  import { allKnownModels, calculateModelCost } from './model-pricing.js';
4
- import { AIIncompleteOutputError, AIRefusalError } from './refusal.js';
4
+ import { AIIncompleteOutputError, AIInvalidRequestError, AIRefusalError } from './refusal.js';
5
5
  /** Compose a request's optional user signal with a timeout, returning a single AbortSignal. */
6
6
  export function combineRequestSignals(userSignal, timeoutMs) {
7
7
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
@@ -194,6 +194,17 @@ function applyOpenAITemperature(body, model, temperature) {
194
194
  return;
195
195
  body.temperature = temperature ?? 0.7;
196
196
  }
197
+ /**
198
+ * `reasoning_effort` is sent ONLY when explicitly configured — no model
199
+ * detection. A model that rejects the parameter fails loudly (HTTP 400)
200
+ * instead of silently running without reasoning, so a misconfigured prompt
201
+ * is visible immediately.
202
+ */
203
+ function applyOpenAIReasoningEffort(body, reasoningEffort) {
204
+ if (reasoningEffort) {
205
+ body.reasoning_effort = reasoningEffort;
206
+ }
207
+ }
197
208
  function anthropicUsageToTokenUsage(usage) {
198
209
  return {
199
210
  inputTokens: usage.input_tokens,
@@ -237,6 +248,197 @@ async function executeRequestWithRetry(args) {
237
248
  message: `${args.errorLabel} request failed`,
238
249
  });
239
250
  }
251
+ // ── Provider tool-calling helpers ──
252
+ /** Portable tool-name grammar (C9). The reserved `flow__` prefix is enforced at chat bind time, not here. */
253
+ const CALLER_TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
254
+ /** Validate caller-supplied tool definitions: portable name grammar + uniqueness. */
255
+ function validateCallerTools(tools) {
256
+ const seen = new Set();
257
+ for (const tool of tools) {
258
+ if (!CALLER_TOOL_NAME_PATTERN.test(tool.name)) {
259
+ throw new AIInvalidRequestError({
260
+ reason: 'tool_name_invalid',
261
+ message: `Tool name "${tool.name}" does not match ^[A-Za-z][A-Za-z0-9_-]{0,63}$`,
262
+ });
263
+ }
264
+ if (seen.has(tool.name)) {
265
+ throw new AIInvalidRequestError({
266
+ reason: 'duplicate_tool_name',
267
+ message: `Duplicate tool name "${tool.name}"`,
268
+ });
269
+ }
270
+ seen.add(tool.name);
271
+ }
272
+ }
273
+ /** Normalize a provider-specific finish/stop reason into the framework's vocabulary. */
274
+ export function normalizeFinishReason(raw) {
275
+ switch (raw) {
276
+ case 'stop':
277
+ case 'end_turn':
278
+ case 'stop_sequence':
279
+ return 'stop';
280
+ case 'length':
281
+ case 'max_tokens':
282
+ return 'length';
283
+ case 'tool_calls':
284
+ case 'tool_use':
285
+ return 'tool_calls';
286
+ case 'refusal':
287
+ case 'content_filter':
288
+ return 'refusal';
289
+ default:
290
+ return 'other';
291
+ }
292
+ }
293
+ /** Guard used by both adapters: caller tools cannot coexist with the tool-transport structured output (§6.6). */
294
+ function assertNoStructuredOutputToolConflict(request) {
295
+ if (request.structuredOutputTransport === 'tool') {
296
+ throw new AIInvalidRequestError({
297
+ reason: 'caller_tools_conflict_with_structured_output_tool',
298
+ message: "Caller tools cannot be combined with structuredOutputTransport: 'tool'.",
299
+ });
300
+ }
301
+ }
302
+ /** Map framework ChatMessage[] to OpenAI chat-completions messages. */
303
+ function toOpenAIMessages(request) {
304
+ const messages = [];
305
+ if (request.system)
306
+ messages.push({ role: 'system', content: request.system });
307
+ if (request.messages && request.messages.length > 0) {
308
+ for (const msg of request.messages) {
309
+ if (msg.role === 'tool') {
310
+ messages.push({ role: 'tool', tool_call_id: msg.toolCallId, content: msg.content });
311
+ }
312
+ else if (msg.role === 'assistant') {
313
+ const entry = {
314
+ role: 'assistant',
315
+ content: msg.content.length > 0 ? msg.content : null,
316
+ };
317
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
318
+ entry.tool_calls = msg.toolCalls.map((tc) => ({
319
+ id: tc.id,
320
+ type: 'function',
321
+ function: {
322
+ name: tc.name,
323
+ arguments: tc.argumentsStatus === 'parsed'
324
+ ? JSON.stringify(tc.arguments ?? {})
325
+ : tc.rawArguments,
326
+ },
327
+ }));
328
+ }
329
+ messages.push(entry);
330
+ }
331
+ else {
332
+ messages.push({ role: 'user', content: msg.content });
333
+ }
334
+ }
335
+ }
336
+ else {
337
+ messages.push({ role: 'user', content: request.prompt });
338
+ }
339
+ return messages;
340
+ }
341
+ /** Map AIToolChoice to the OpenAI `tool_choice` wire value. */
342
+ function buildOpenAIToolChoice(toolChoice) {
343
+ if (toolChoice === undefined)
344
+ return undefined;
345
+ if (toolChoice === 'auto' || toolChoice === 'none')
346
+ return toolChoice;
347
+ return { type: 'function', function: { name: toolChoice.function.name } };
348
+ }
349
+ /** Parse OpenAI tool_calls into framework AIToolCall records (§6.4). */
350
+ function parseOpenAIToolCalls(toolCalls) {
351
+ if (!toolCalls)
352
+ return [];
353
+ const result = [];
354
+ for (const tc of toolCalls) {
355
+ const name = tc.function?.name;
356
+ if (!name)
357
+ continue;
358
+ const id = tc.id ?? crypto.randomUUID();
359
+ const rawArguments = tc.function?.arguments ?? '';
360
+ try {
361
+ const parsed = rawArguments.trim().length === 0 ? {} : JSON.parse(rawArguments);
362
+ result.push({ id, name, argumentsStatus: 'parsed', arguments: parsed });
363
+ }
364
+ catch (err) {
365
+ result.push({
366
+ id,
367
+ name,
368
+ argumentsStatus: 'invalid',
369
+ rawArguments: rawArguments.slice(0, 4096),
370
+ parseError: (err instanceof Error ? err.message : String(err)).slice(0, 512),
371
+ });
372
+ }
373
+ }
374
+ return result;
375
+ }
376
+ /** Map framework ChatMessage[] to Anthropic messages (tool_use / tool_result blocks). */
377
+ function toAnthropicMessages(request) {
378
+ if (!request.messages || request.messages.length === 0) {
379
+ return [{ role: 'user', content: request.prompt }];
380
+ }
381
+ const out = [];
382
+ for (const msg of request.messages) {
383
+ if (msg.role === 'tool') {
384
+ const block = { type: 'tool_result', tool_use_id: msg.toolCallId, content: msg.content };
385
+ const last = out[out.length - 1];
386
+ if (last && last.role === 'user' && Array.isArray(last.content)) {
387
+ last.content.push(block);
388
+ }
389
+ else {
390
+ out.push({ role: 'user', content: [block] });
391
+ }
392
+ }
393
+ else if (msg.role === 'assistant') {
394
+ const blocks = [];
395
+ if (msg.content.length > 0)
396
+ blocks.push({ type: 'text', text: msg.content });
397
+ if (msg.toolCalls) {
398
+ for (const tc of msg.toolCalls) {
399
+ blocks.push({
400
+ type: 'tool_use',
401
+ id: tc.id,
402
+ name: tc.name,
403
+ input: tc.argumentsStatus === 'parsed' ? (tc.arguments ?? {}) : {},
404
+ });
405
+ }
406
+ }
407
+ out.push({ role: 'assistant', content: blocks.length > 0 ? blocks : msg.content });
408
+ }
409
+ else {
410
+ out.push({ role: 'user', content: msg.content });
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+ /** Map AIToolChoice + execution options to the Anthropic `tool_choice` wire value. */
416
+ function buildAnthropicToolChoice(toolChoice, toolExecution) {
417
+ const disableParallel = toolExecution?.parallelToolCalls === false;
418
+ if (toolChoice === undefined && !disableParallel)
419
+ return undefined;
420
+ let base;
421
+ if (toolChoice === undefined || toolChoice === 'auto')
422
+ base = { type: 'auto' };
423
+ else if (toolChoice === 'none')
424
+ base = { type: 'none' };
425
+ else
426
+ base = { type: 'tool', name: toolChoice.function.name };
427
+ if (disableParallel && base.type !== 'none')
428
+ base.disable_parallel_tool_use = true;
429
+ return base;
430
+ }
431
+ /** Parse Anthropic tool_use content blocks into framework AIToolCall records. */
432
+ function parseAnthropicToolUse(content) {
433
+ return content
434
+ .filter((c) => c.type === 'tool_use')
435
+ .map((c) => ({
436
+ id: c.id ?? crypto.randomUUID(),
437
+ name: c.name ?? '',
438
+ argumentsStatus: 'parsed',
439
+ arguments: c.input ?? {},
440
+ }));
441
+ }
240
442
  export function createOpenAIAdapter(config) {
241
443
  const defaultModel = config.model ?? 'gpt-4o';
242
444
  const defaultEmbeddingModel = config.embeddingModel ?? 'text-embedding-3-small';
@@ -244,43 +446,53 @@ export function createOpenAIAdapter(config) {
244
446
  const defaultTimeout = config.requestTimeout ?? 120_000;
245
447
  return {
246
448
  name: 'openai',
449
+ capabilities: {
450
+ tools: true,
451
+ streamingTools: false,
452
+ parallelToolCalls: true,
453
+ parallelToolCallControl: true,
454
+ namedToolChoice: true,
455
+ },
247
456
  async complete(request) {
248
457
  const model = request.model ?? defaultModel;
249
- const messages = [];
250
- if (request.system) {
251
- messages.push({ role: 'system', content: request.system });
252
- }
253
- // Multi-turn path: when `messages` is supplied, append them verbatim
254
- // (preserving role + order). Single-turn path (legacy): build a single
255
- // user message from `prompt`.
256
- if (request.messages && request.messages.length > 0) {
257
- for (const msg of request.messages) {
258
- messages.push({ role: msg.role, content: msg.content });
259
- }
260
- }
261
- else {
262
- messages.push({ role: 'user', content: request.prompt });
263
- }
458
+ const messages = toOpenAIMessages(request);
264
459
  const body = {
265
460
  model,
266
461
  messages,
267
462
  };
268
463
  applyOpenAITemperature(body, model, request.temperature);
464
+ applyOpenAIReasoningEffort(body, request.reasoningEffort);
269
465
  applyOpenAITokenLimit(body, model, request.maxTokens);
270
- const structuredToolSchema = shouldUseOpenAIStructuredTool({ request })
271
- ? request.responseSchema
272
- : undefined;
273
- if (structuredToolSchema) {
274
- body.tools = [buildOpenAIStructuredOutputTool(structuredToolSchema)];
275
- body.tool_choice = {
466
+ if (request.tools && request.tools.length > 0) {
467
+ assertNoStructuredOutputToolConflict(request);
468
+ validateCallerTools(request.tools);
469
+ body.tools = request.tools.map((t) => ({
276
470
  type: 'function',
277
- function: { name: OPENAI_STRUCTURED_OUTPUT_TOOL_NAME },
278
- };
471
+ function: { name: t.name, description: t.description, parameters: t.parameters },
472
+ }));
473
+ const callerToolChoice = buildOpenAIToolChoice(request.toolChoice);
474
+ if (callerToolChoice !== undefined)
475
+ body.tool_choice = callerToolChoice;
476
+ if (request.toolExecution?.parallelToolCalls === false) {
477
+ body.parallel_tool_calls = false;
478
+ }
279
479
  }
280
480
  else {
281
- const responseFormat = buildOpenAIResponseFormat(request);
282
- if (responseFormat) {
283
- body.response_format = responseFormat;
481
+ const structuredToolSchema = shouldUseOpenAIStructuredTool({ request })
482
+ ? request.responseSchema
483
+ : undefined;
484
+ if (structuredToolSchema) {
485
+ body.tools = [buildOpenAIStructuredOutputTool(structuredToolSchema)];
486
+ body.tool_choice = {
487
+ type: 'function',
488
+ function: { name: OPENAI_STRUCTURED_OUTPUT_TOOL_NAME },
489
+ };
490
+ }
491
+ else {
492
+ const responseFormat = buildOpenAIResponseFormat(request);
493
+ if (responseFormat) {
494
+ body.response_format = responseFormat;
495
+ }
284
496
  }
285
497
  }
286
498
  if (typeof request.seed === 'number') {
@@ -313,6 +525,15 @@ export function createOpenAIAdapter(config) {
313
525
  usage,
314
526
  });
315
527
  }
528
+ if (request.tools && request.tools.length > 0 && choice.finish_reason === 'tool_calls') {
529
+ return {
530
+ content,
531
+ model: data.model,
532
+ usage,
533
+ finishReason: 'tool_calls',
534
+ toolCalls: parseOpenAIToolCalls(choice.message.tool_calls),
535
+ };
536
+ }
316
537
  // Only treat `finish_reason === 'length'` as a hard failure when the
317
538
  // caller asked for structured JSON output. For free-text generation,
318
539
  // a `length` finish is a partial-success: return what we have rather
@@ -360,6 +581,7 @@ export function createOpenAIAdapter(config) {
360
581
  stream_options: { include_usage: true },
361
582
  };
362
583
  applyOpenAITemperature(body, model, request.temperature);
584
+ applyOpenAIReasoningEffort(body, request.reasoningEffort);
363
585
  applyOpenAITokenLimit(body, model, request.maxTokens);
364
586
  const responseFormat = buildOpenAIResponseFormat(request);
365
587
  if (responseFormat) {
@@ -463,12 +685,15 @@ export function createAnthropicAdapter(config) {
463
685
  const defaultTimeout = config.requestTimeout ?? 120_000;
464
686
  return {
465
687
  name: 'anthropic',
688
+ capabilities: {
689
+ tools: true,
690
+ streamingTools: false,
691
+ parallelToolCalls: true,
692
+ parallelToolCallControl: true,
693
+ namedToolChoice: true,
694
+ },
466
695
  async complete(request) {
467
- // Multi-turn path: when `messages` is supplied, send them verbatim.
468
- // Single-turn path: synthesize a single user message from `prompt`.
469
- const messages = request.messages && request.messages.length > 0
470
- ? request.messages.map((m) => ({ role: m.role, content: m.content }))
471
- : [{ role: 'user', content: request.prompt }];
696
+ const messages = toAnthropicMessages(request);
472
697
  const body = {
473
698
  model: request.model ?? defaultModel,
474
699
  messages,
@@ -485,6 +710,18 @@ export function createAnthropicAdapter(config) {
485
710
  },
486
711
  };
487
712
  }
713
+ if (request.tools && request.tools.length > 0) {
714
+ assertNoStructuredOutputToolConflict(request);
715
+ validateCallerTools(request.tools);
716
+ body.tools = request.tools.map((t) => ({
717
+ name: t.name,
718
+ description: t.description,
719
+ input_schema: t.parameters,
720
+ }));
721
+ const callerToolChoice = buildAnthropicToolChoice(request.toolChoice, request.toolExecution);
722
+ if (callerToolChoice !== undefined)
723
+ body.tool_choice = callerToolChoice;
724
+ }
488
725
  const resp = await executeRequestWithRetry({
489
726
  providerName: 'anthropic',
490
727
  errorLabel: 'Anthropic API',
@@ -503,7 +740,7 @@ export function createAnthropicAdapter(config) {
503
740
  const usage = anthropicUsageToTokenUsage(data.usage);
504
741
  const text = data.content
505
742
  .filter((c) => c.type === 'text')
506
- .map((c) => c.text)
743
+ .map((c) => c.text ?? '')
507
744
  .join('');
508
745
  if (data.stop_reason === 'refusal') {
509
746
  throw new AIRefusalError({
@@ -513,6 +750,15 @@ export function createAnthropicAdapter(config) {
513
750
  usage,
514
751
  });
515
752
  }
753
+ if (request.tools && request.tools.length > 0 && data.stop_reason === 'tool_use') {
754
+ return {
755
+ content: text,
756
+ model: data.model,
757
+ usage,
758
+ finishReason: 'tool_use',
759
+ toolCalls: parseAnthropicToolUse(data.content),
760
+ };
761
+ }
516
762
  // Mirror the OpenAI adapter: only throw on max_tokens when the caller
517
763
  // requested structured JSON output. Free-text completions should return
518
764
  // the partial content.