@axlsdk/axl 0.4.0 → 0.5.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 CHANGED
@@ -43,6 +43,7 @@ const researcher = agent({
43
43
  model: 'openai:gpt-4o',
44
44
  system: 'You are a research assistant.',
45
45
  tools: [calculator],
46
+ thinking: 'high',
46
47
  maxTurns: 10,
47
48
  timeout: '30s',
48
49
  temperature: 0.7,
@@ -61,6 +62,31 @@ const dynamicAgent = agent({
61
62
  });
62
63
  ```
63
64
 
65
+ #### Thinking (cross-provider reasoning control)
66
+
67
+ The `thinking` parameter provides a unified way to control reasoning depth across all providers:
68
+
69
+ ```typescript
70
+ // Simple levels — works on any provider
71
+ const reasoner = agent({
72
+ model: 'anthropic:claude-sonnet-4-5',
73
+ system: 'You are a careful analyst.',
74
+ thinking: 'high', // 'low' | 'medium' | 'high' | 'max'
75
+ });
76
+
77
+ // Explicit budget (in tokens)
78
+ const budgetReasoner = agent({
79
+ model: 'google:gemini-2.5-flash',
80
+ system: 'Think step by step.',
81
+ thinking: { budgetTokens: 5000 },
82
+ });
83
+
84
+ // Per-call override
85
+ const result = await reasoner.ask('Analyze this data', { thinking: 'low' });
86
+ ```
87
+
88
+ Each provider maps `thinking` to its native API: `reasoning_effort` (OpenAI), `budget_tokens` (Anthropic), `thinkingBudget` (Gemini). See [docs/providers.md](../../docs/providers.md) for the full mapping table.
89
+
64
90
  ### `workflow(config)`
65
91
 
66
92
  Define a named workflow with typed input/output:
package/dist/index.cjs CHANGED
@@ -331,6 +331,24 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
331
331
  function isReasoningModel(model) {
332
332
  return /^(o1|o3|o4-mini)/.test(model);
333
333
  }
334
+ function thinkingToReasoningEffort(thinking) {
335
+ if (typeof thinking === "object") {
336
+ const budget = thinking.budgetTokens;
337
+ if (budget <= 1024) return "low";
338
+ if (budget <= 8192) return "medium";
339
+ return "high";
340
+ }
341
+ switch (thinking) {
342
+ case "low":
343
+ return "low";
344
+ case "medium":
345
+ return "medium";
346
+ case "high":
347
+ return "high";
348
+ case "max":
349
+ return "xhigh";
350
+ }
351
+ }
334
352
  var OpenAIProvider = class {
335
353
  name = "openai";
336
354
  baseUrl;
@@ -433,7 +451,9 @@ var OpenAIProvider = class {
433
451
  if (options.stop) body.stop = options.stop;
434
452
  if (options.tools && options.tools.length > 0) {
435
453
  body.tools = options.tools;
436
- body.parallel_tool_calls = true;
454
+ if (!reasoning) {
455
+ body.parallel_tool_calls = true;
456
+ }
437
457
  }
438
458
  if (options.toolChoice !== void 0) {
439
459
  body.tool_choice = options.toolChoice;
@@ -441,8 +461,11 @@ var OpenAIProvider = class {
441
461
  if (options.responseFormat) {
442
462
  body.response_format = options.responseFormat;
443
463
  }
444
- if (options.reasoningEffort) {
445
- body.reasoning_effort = options.reasoningEffort;
464
+ if (reasoning) {
465
+ const effort = options.thinking ? thinkingToReasoningEffort(options.thinking) : options.reasoningEffort;
466
+ if (effort) {
467
+ body.reasoning_effort = effort;
468
+ }
446
469
  }
447
470
  if (stream) {
448
471
  body.stream_options = { include_usage: true };
@@ -633,8 +656,11 @@ var OpenAIResponsesProvider = class {
633
656
  body.tool_choice = options.toolChoice;
634
657
  }
635
658
  }
636
- if (options.reasoningEffort) {
637
- body.reasoning = { effort: options.reasoningEffort };
659
+ if (reasoning) {
660
+ const effort = options.thinking ? thinkingToReasoningEffort(options.thinking) : options.reasoningEffort;
661
+ if (effort) {
662
+ body.reasoning = { effort };
663
+ }
638
664
  }
639
665
  if (options.responseFormat) {
640
666
  body.text = { format: this.mapResponseFormat(options.responseFormat) };
@@ -868,6 +894,24 @@ function estimateAnthropicCost(model, inputTokens, outputTokens, cacheReadTokens
868
894
  const inputCost = (inputTokens - cacheRead - cacheWrite) * inputRate + cacheRead * inputRate * 0.1 + cacheWrite * inputRate * 1.25;
869
895
  return inputCost + outputTokens * outputRate;
870
896
  }
897
+ var THINKING_BUDGETS = {
898
+ low: 1024,
899
+ medium: 5e3,
900
+ high: 1e4,
901
+ // 30000 (not 32000) to stay under the 32K max_tokens limit on Opus 4/4.1.
902
+ // With auto-bump (+1024), max_tokens becomes 31024 which fits all models.
903
+ max: 3e4
904
+ };
905
+ function thinkingToBudgetTokens(thinking) {
906
+ if (typeof thinking === "string") return THINKING_BUDGETS[thinking] ?? 5e3;
907
+ return thinking.budgetTokens;
908
+ }
909
+ function supportsAdaptiveThinking(model) {
910
+ return model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
911
+ }
912
+ function supportsMaxEffort(model) {
913
+ return model.startsWith("claude-opus-4-6");
914
+ }
871
915
  var AnthropicProvider = class {
872
916
  name = "anthropic";
873
917
  baseUrl;
@@ -957,7 +1001,7 @@ var AnthropicProvider = class {
957
1001
  if (systemText) {
958
1002
  body.system = systemText;
959
1003
  }
960
- if (options.temperature !== void 0) {
1004
+ if (options.temperature !== void 0 && !options.thinking) {
961
1005
  body.temperature = options.temperature;
962
1006
  }
963
1007
  if (options.stop) {
@@ -966,6 +1010,23 @@ var AnthropicProvider = class {
966
1010
  if (options.tools && options.tools.length > 0) {
967
1011
  body.tools = options.tools.map((t) => this.mapToolDefinition(t));
968
1012
  }
1013
+ if (options.toolChoice !== void 0) {
1014
+ body.tool_choice = this.mapToolChoice(options.toolChoice);
1015
+ }
1016
+ if (options.thinking) {
1017
+ if (typeof options.thinking === "string" && supportsAdaptiveThinking(options.model) && // 'max' effort is only supported on Opus 4.6; Sonnet 4.6 falls back to manual mode
1018
+ (options.thinking !== "max" || supportsMaxEffort(options.model))) {
1019
+ body.thinking = { type: "adaptive" };
1020
+ body.output_config = { effort: options.thinking };
1021
+ } else {
1022
+ const budgetTokens = thinkingToBudgetTokens(options.thinking);
1023
+ body.thinking = { type: "enabled", budget_tokens: budgetTokens };
1024
+ const currentMax = body.max_tokens;
1025
+ if (currentMax < budgetTokens + 1024) {
1026
+ body.max_tokens = budgetTokens + 1024;
1027
+ }
1028
+ }
1029
+ }
969
1030
  if (options.responseFormat && options.responseFormat.type !== "text") {
970
1031
  const jsonInstruction = "You must respond with valid JSON only. No markdown fences, no extra text.";
971
1032
  body.system = body.system ? `${body.system}
@@ -1061,6 +1122,22 @@ ${jsonInstruction}` : jsonInstruction;
1061
1122
  input_schema: tool2.function.parameters
1062
1123
  };
1063
1124
  }
1125
+ /**
1126
+ * Map Axl's ToolChoice to Anthropic's tool_choice format.
1127
+ *
1128
+ * Axl (OpenAI format) → Anthropic format
1129
+ * 'auto' → { type: 'auto' }
1130
+ * 'none' → { type: 'none' }
1131
+ * 'required' → { type: 'any' }
1132
+ * { type:'function', function: { name } } → { type: 'tool', name }
1133
+ */
1134
+ mapToolChoice(choice) {
1135
+ if (typeof choice === "string") {
1136
+ if (choice === "required") return { type: "any" };
1137
+ return { type: choice };
1138
+ }
1139
+ return { type: "tool", name: choice.function.name };
1140
+ }
1064
1141
  // ---------------------------------------------------------------------------
1065
1142
  // Internal: response parsing
1066
1143
  // ---------------------------------------------------------------------------
@@ -1238,6 +1315,16 @@ function estimateGeminiCost(model, inputTokens, outputTokens, cachedTokens) {
1238
1315
  const inputCost = (inputTokens - cached) * inputRate + cached * inputRate * 0.1;
1239
1316
  return inputCost + outputTokens * outputRate;
1240
1317
  }
1318
+ var THINKING_BUDGETS2 = {
1319
+ low: 1024,
1320
+ medium: 5e3,
1321
+ high: 1e4,
1322
+ max: 24576
1323
+ };
1324
+ function thinkingToBudgetTokens2(thinking) {
1325
+ if (typeof thinking === "string") return THINKING_BUDGETS2[thinking] ?? 5e3;
1326
+ return thinking.budgetTokens;
1327
+ }
1241
1328
  var GeminiProvider = class {
1242
1329
  name = "google";
1243
1330
  baseUrl;
@@ -1351,6 +1438,17 @@ var GeminiProvider = class {
1351
1438
  if (Object.keys(generationConfig).length > 0) {
1352
1439
  body.generationConfig = generationConfig;
1353
1440
  }
1441
+ if (options.thinking) {
1442
+ generationConfig.thinkingConfig = {
1443
+ thinkingBudget: thinkingToBudgetTokens2(options.thinking)
1444
+ };
1445
+ if (!body.generationConfig) {
1446
+ body.generationConfig = generationConfig;
1447
+ }
1448
+ }
1449
+ if (options.toolChoice !== void 0) {
1450
+ body.toolConfig = { functionCallingConfig: this.mapToolChoice(options.toolChoice) };
1451
+ }
1354
1452
  return body;
1355
1453
  }
1356
1454
  /**
@@ -1442,6 +1540,25 @@ var GeminiProvider = class {
1442
1540
  }
1443
1541
  return merged;
1444
1542
  }
1543
+ /**
1544
+ * Map Axl's ToolChoice to Gemini's functionCallingConfig format.
1545
+ *
1546
+ * - 'auto' → { mode: 'AUTO' }
1547
+ * - 'none' → { mode: 'NONE' }
1548
+ * - 'required' → { mode: 'ANY' }
1549
+ * - { type: 'function', function: { name } } → { mode: 'ANY', allowedFunctionNames: [name] }
1550
+ */
1551
+ mapToolChoice(choice) {
1552
+ if (typeof choice === "string") {
1553
+ const modeMap = {
1554
+ auto: "AUTO",
1555
+ none: "NONE",
1556
+ required: "ANY"
1557
+ };
1558
+ return { mode: modeMap[choice] ?? "AUTO" };
1559
+ }
1560
+ return { mode: "ANY", allowedFunctionNames: [choice.function.name] };
1561
+ }
1445
1562
  mapToolDefinition(tool2) {
1446
1563
  return {
1447
1564
  name: tool2.function.name,
@@ -2024,7 +2141,13 @@ var WorkflowContext = class {
2024
2141
  model: agent2.resolveModel(resolveCtx),
2025
2142
  cost: costAfter - costBefore,
2026
2143
  duration: Date.now() - startTime,
2027
- promptVersion: agent2._config.version
2144
+ promptVersion: agent2._config.version,
2145
+ temperature: options?.temperature ?? agent2._config.temperature,
2146
+ maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
2147
+ thinking: options?.thinking ?? agent2._config.thinking,
2148
+ reasoningEffort: options?.reasoningEffort ?? agent2._config.reasoningEffort,
2149
+ toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
2150
+ stop: options?.stop ?? agent2._config.stop
2028
2151
  });
2029
2152
  return result;
2030
2153
  });
@@ -2149,11 +2272,21 @@ Please fix and try again.`;
2149
2272
  throw new TimeoutError("ctx.ask()", timeoutMs);
2150
2273
  }
2151
2274
  turns++;
2275
+ const thinking = options?.thinking ?? agent2._config.thinking;
2276
+ if (thinking && typeof thinking === "object" && thinking.budgetTokens <= 0) {
2277
+ throw new Error(
2278
+ `thinking.budgetTokens must be a positive number, got ${thinking.budgetTokens}`
2279
+ );
2280
+ }
2152
2281
  const chatOptions = {
2153
2282
  model,
2154
- temperature: agent2._config.temperature,
2283
+ temperature: options?.temperature ?? agent2._config.temperature,
2155
2284
  tools: toolDefs.length > 0 ? toolDefs : void 0,
2156
- maxTokens: 4096,
2285
+ maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
2286
+ thinking,
2287
+ reasoningEffort: options?.reasoningEffort ?? agent2._config.reasoningEffort,
2288
+ toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
2289
+ stop: options?.stop ?? agent2._config.stop,
2157
2290
  signal: this.currentSignal
2158
2291
  };
2159
2292
  if (options?.schema && toolDefs.length === 0) {
@@ -2252,10 +2385,11 @@ Please fix and try again.`;
2252
2385
  }
2253
2386
  }
2254
2387
  const handoffStart = Date.now();
2388
+ const handoffOptions = options ? { schema: options.schema, retries: options.retries, metadata: options.metadata } : void 0;
2255
2389
  const handoffFn = () => this.executeAgentCall(
2256
2390
  descriptor.agent,
2257
2391
  handoffPrompt,
2258
- options,
2392
+ handoffOptions,
2259
2393
  0,
2260
2394
  void 0,
2261
2395
  void 0,