@gullabs/xai 0.8.0 → 0.9.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/dist/index.d.cts CHANGED
@@ -35,6 +35,20 @@ type XaiProviderOptions = {
35
35
  tools?: Array<XaiWebSearchTool | XaiXSearchTool>;
36
36
  /** xAI-only; Gemini has no parallel-tool knob. */
37
37
  parallelToolCalls?: boolean;
38
+ /**
39
+ * Responses API `tool_choice` for the server-side search tools; `required`
40
+ * forces at least one search. Requires `tools`, and cannot be combined
41
+ * with the request-level `toolChoice`.
42
+ */
43
+ toolChoice?: 'auto' | 'required' | 'none';
44
+ /**
45
+ * Responses API `max_turns`: the cap on agentic tool-calling turns for the
46
+ * server-side search tools. A turn can run several searches, so this is
47
+ * not a search count. Requires `tools`. As of 2026-10-02 xAI did not
48
+ * enforce it on grok-4.5 / 4.6 / 4.7; assert on
49
+ * `usage.details.web_search_calls` rather than trusting the cap.
50
+ */
51
+ maxTurns?: number;
38
52
  };
39
53
  declare module '@gullabs/core' {
40
54
  interface ProviderOptionsMap {
@@ -164,6 +178,7 @@ interface XaiResponseCreateParams {
164
178
  type: 'function';
165
179
  name: string;
166
180
  };
181
+ max_turns?: number;
167
182
  parallel_tool_calls?: boolean;
168
183
  }
169
184
  /** A single summary-text segment of a `type: 'reasoning'` output item. */
@@ -621,6 +636,12 @@ declare const Grok45ConfigSchema: z.ZodObject<{
621
636
  type: z.ZodLiteral<"web_search">;
622
637
  }, z.core.$strict>]>], null>]>>;
623
638
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
639
+ toolChoice: z.ZodOptional<z.ZodEnum<{
640
+ auto: "auto";
641
+ required: "required";
642
+ none: "none";
643
+ }>>;
644
+ maxTurns: z.ZodOptional<z.ZodNumber>;
624
645
  }, z.core.$strict>>;
625
646
  }, z.core.$strict>>;
626
647
  }, z.core.$strict>;
@@ -758,6 +779,12 @@ declare const Grok46ConfigSchema: z.ZodObject<{
758
779
  type: z.ZodLiteral<"web_search">;
759
780
  }, z.core.$strict>]>], null>]>>;
760
781
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
782
+ toolChoice: z.ZodOptional<z.ZodEnum<{
783
+ auto: "auto";
784
+ required: "required";
785
+ none: "none";
786
+ }>>;
787
+ maxTurns: z.ZodOptional<z.ZodNumber>;
761
788
  }, z.core.$strict>>;
762
789
  }, z.core.$strict>>;
763
790
  }, z.core.$strict>;
@@ -896,6 +923,12 @@ declare const Grok47ConfigSchema: z.ZodObject<{
896
923
  type: z.ZodLiteral<"web_search">;
897
924
  }, z.core.$strict>]>], null>]>>;
898
925
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
926
+ toolChoice: z.ZodOptional<z.ZodEnum<{
927
+ auto: "auto";
928
+ required: "required";
929
+ none: "none";
930
+ }>>;
931
+ maxTurns: z.ZodOptional<z.ZodNumber>;
899
932
  }, z.core.$strict>>;
900
933
  }, z.core.$strict>>;
901
934
  }, z.core.$strict>;
package/dist/index.d.ts CHANGED
@@ -35,6 +35,20 @@ type XaiProviderOptions = {
35
35
  tools?: Array<XaiWebSearchTool | XaiXSearchTool>;
36
36
  /** xAI-only; Gemini has no parallel-tool knob. */
37
37
  parallelToolCalls?: boolean;
38
+ /**
39
+ * Responses API `tool_choice` for the server-side search tools; `required`
40
+ * forces at least one search. Requires `tools`, and cannot be combined
41
+ * with the request-level `toolChoice`.
42
+ */
43
+ toolChoice?: 'auto' | 'required' | 'none';
44
+ /**
45
+ * Responses API `max_turns`: the cap on agentic tool-calling turns for the
46
+ * server-side search tools. A turn can run several searches, so this is
47
+ * not a search count. Requires `tools`. As of 2026-10-02 xAI did not
48
+ * enforce it on grok-4.5 / 4.6 / 4.7; assert on
49
+ * `usage.details.web_search_calls` rather than trusting the cap.
50
+ */
51
+ maxTurns?: number;
38
52
  };
39
53
  declare module '@gullabs/core' {
40
54
  interface ProviderOptionsMap {
@@ -164,6 +178,7 @@ interface XaiResponseCreateParams {
164
178
  type: 'function';
165
179
  name: string;
166
180
  };
181
+ max_turns?: number;
167
182
  parallel_tool_calls?: boolean;
168
183
  }
169
184
  /** A single summary-text segment of a `type: 'reasoning'` output item. */
@@ -621,6 +636,12 @@ declare const Grok45ConfigSchema: z.ZodObject<{
621
636
  type: z.ZodLiteral<"web_search">;
622
637
  }, z.core.$strict>]>], null>]>>;
623
638
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
639
+ toolChoice: z.ZodOptional<z.ZodEnum<{
640
+ auto: "auto";
641
+ required: "required";
642
+ none: "none";
643
+ }>>;
644
+ maxTurns: z.ZodOptional<z.ZodNumber>;
624
645
  }, z.core.$strict>>;
625
646
  }, z.core.$strict>>;
626
647
  }, z.core.$strict>;
@@ -758,6 +779,12 @@ declare const Grok46ConfigSchema: z.ZodObject<{
758
779
  type: z.ZodLiteral<"web_search">;
759
780
  }, z.core.$strict>]>], null>]>>;
760
781
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
782
+ toolChoice: z.ZodOptional<z.ZodEnum<{
783
+ auto: "auto";
784
+ required: "required";
785
+ none: "none";
786
+ }>>;
787
+ maxTurns: z.ZodOptional<z.ZodNumber>;
761
788
  }, z.core.$strict>>;
762
789
  }, z.core.$strict>>;
763
790
  }, z.core.$strict>;
@@ -896,6 +923,12 @@ declare const Grok47ConfigSchema: z.ZodObject<{
896
923
  type: z.ZodLiteral<"web_search">;
897
924
  }, z.core.$strict>]>], null>]>>;
898
925
  parallelToolCalls: z.ZodOptional<z.ZodBoolean>;
926
+ toolChoice: z.ZodOptional<z.ZodEnum<{
927
+ auto: "auto";
928
+ required: "required";
929
+ none: "none";
930
+ }>>;
931
+ maxTurns: z.ZodOptional<z.ZodNumber>;
899
932
  }, z.core.$strict>>;
900
933
  }, z.core.$strict>>;
901
934
  }, z.core.$strict>;
package/dist/index.js CHANGED
@@ -115,6 +115,14 @@ var XaiProviderOptionsSchema = z.strictObject({
115
115
  parallelToolCalls: z.boolean().optional().meta({
116
116
  title: "Parallel Tool Calls",
117
117
  description: "xAI Responses parallel_tool_calls. Not a generic contract field."
118
+ }),
119
+ toolChoice: z.enum(["auto", "required", "none"]).optional().meta({
120
+ title: "Server Tool Choice",
121
+ description: "xAI Responses `tool_choice` for the server-side search tools; `required` forces at least one search, `none` disables them. Requires `tools`. Cannot be combined with function tools, file attachments or the request-level toolChoice."
122
+ }),
123
+ maxTurns: z.number().int().min(1).optional().meta({
124
+ title: "Max Turns",
125
+ description: "xAI Responses `max_turns`: cap on agentic tool-calling turns for the server-side search tools. Requires `tools`. A turn can run several searches. Not enforced by xAI as of 2026-10-02."
118
126
  })
119
127
  }).meta({
120
128
  title: "xAI Provider Options",
@@ -262,6 +270,7 @@ var grok45ModelDescriptor = {
262
270
  sampling: "tunable",
263
271
  caching: { explicit: false, minTokens: 0 },
264
272
  grounding: true,
273
+ structuredOutputWithTools: true,
265
274
  functionCalling: true,
266
275
  serviceTiers: ["priority"]
267
276
  },
@@ -307,6 +316,7 @@ var grok47ModelDescriptor = {
307
316
  sampling: "tunable",
308
317
  caching: { explicit: false, minTokens: 0 },
309
318
  grounding: true,
319
+ structuredOutputWithTools: true,
310
320
  functionCalling: true,
311
321
  statelessReasoningReplay: true,
312
322
  serviceTiers: ["priority"]
@@ -321,6 +331,89 @@ var xaiModelDescriptors = [
321
331
  grok47ModelDescriptor
322
332
  ];
323
333
  var xaiRegistry = createModelRegistry(xaiModelDescriptors);
334
+ var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Set([
335
+ "string",
336
+ "number",
337
+ "integer",
338
+ "boolean",
339
+ "object",
340
+ "array",
341
+ "null"
342
+ ]);
343
+ var SINGLE_SCHEMA_KEYWORDS = [
344
+ "additionalProperties",
345
+ "additionalItems",
346
+ "items",
347
+ "contains",
348
+ "not",
349
+ "if",
350
+ "then",
351
+ "else",
352
+ "propertyNames",
353
+ "unevaluatedProperties",
354
+ "unevaluatedItems",
355
+ "contentSchema"
356
+ ];
357
+ var SCHEMA_ARRAY_KEYWORDS = ["items", "prefixItems", "anyOf", "oneOf", "allOf"];
358
+ var SCHEMA_MAP_KEYWORDS = [
359
+ "properties",
360
+ "patternProperties",
361
+ "dependentSchemas",
362
+ // Draft-07 `dependencies`: schema-valued entries are walked; array-valued
363
+ // entries are property-name lists and are skipped by the object check.
364
+ "dependencies",
365
+ "$defs",
366
+ "definitions"
367
+ ];
368
+ function isPlainObject(value) {
369
+ return value !== null && typeof value === "object" && !Array.isArray(value);
370
+ }
371
+ function badSchema(message) {
372
+ return new LlmError(message, { kind: "bad_request", retryable: false, provider: "xai" });
373
+ }
374
+ function assertNode(node, path) {
375
+ const at = path.length > 0 ? path : "<root>";
376
+ if ("nullable" in node) {
377
+ throw badSchema(
378
+ `xAI structured output takes standard JSON Schema, which has no \`nullable\` keyword (found at \`${at}\`). xAI ignores it, so the field would be required and non-null. List 'null' in \`type\` instead, e.g. type: ['string', 'null'].`
379
+ );
380
+ }
381
+ const type = node["type"];
382
+ if (type !== void 0) {
383
+ const names = Array.isArray(type) ? type : [type];
384
+ for (const name of names) {
385
+ if (typeof name !== "string" || !JSON_SCHEMA_TYPES.has(name)) {
386
+ throw badSchema(
387
+ `xAI structured output takes standard JSON Schema; \`type\` ${JSON.stringify(
388
+ name
389
+ )} at \`${at}\` is not a JSON Schema type. Use lowercase string, number, integer, boolean, object, array or null.`
390
+ );
391
+ }
392
+ }
393
+ }
394
+ const child = (segment) => path.length > 0 ? `${path}.${segment}` : segment;
395
+ for (const keyword of SCHEMA_MAP_KEYWORDS) {
396
+ const map = node[keyword];
397
+ if (!isPlainObject(map)) continue;
398
+ for (const [key, member] of Object.entries(map)) {
399
+ if (isPlainObject(member)) assertNode(member, child(`${keyword}.${key}`));
400
+ }
401
+ }
402
+ for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
403
+ const members = node[keyword];
404
+ if (!Array.isArray(members)) continue;
405
+ members.forEach((member, index) => {
406
+ if (isPlainObject(member)) assertNode(member, child(`${keyword}[${index}]`));
407
+ });
408
+ }
409
+ for (const keyword of SINGLE_SCHEMA_KEYWORDS) {
410
+ const member = node[keyword];
411
+ if (isPlainObject(member)) assertNode(member, child(keyword));
412
+ }
413
+ }
414
+ function assertXaiOutputJsonSchema(schema) {
415
+ if (isPlainObject(schema)) assertNode(schema, "");
416
+ }
324
417
  var xaiPricingVersion = "xai-2026-09-25";
325
418
  var XAI_TOOL_RATE_MICRO_USD = {
326
419
  web_search_calls: 5e3,
@@ -537,7 +630,14 @@ function mapPart(p) {
537
630
  return assertNever(p);
538
631
  }
539
632
  }
540
- var XAI_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set(["promptCacheKey", "tools", "parallelToolCalls"]);
633
+ var XAI_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
634
+ "promptCacheKey",
635
+ "tools",
636
+ "parallelToolCalls",
637
+ "toolChoice",
638
+ "maxTurns"
639
+ ]);
640
+ var XAI_SERVER_TOOL_CHOICES = /* @__PURE__ */ new Set(["auto", "required", "none"]);
541
641
  function mapXaiProviderOptions(xaiOpts, model) {
542
642
  if (xaiOpts === void 0) {
543
643
  return {};
@@ -552,7 +652,7 @@ function mapXaiProviderOptions(xaiOpts, model) {
552
652
  throw badXaiRequest(
553
653
  `providerOptions.xai contains unsupported keys [${unknownKeys.join(
554
654
  ", "
555
- )}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls.`
655
+ )}] for model "${model}". Allowed keys: promptCacheKey, tools, parallelToolCalls, toolChoice, maxTurns.`
556
656
  );
557
657
  }
558
658
  const mapped = {};
@@ -575,6 +675,34 @@ function mapXaiProviderOptions(xaiOpts, model) {
575
675
  }
576
676
  mapped.parallelToolCalls = xaiOpts["parallelToolCalls"];
577
677
  }
678
+ const toolChoice = xaiOpts["toolChoice"];
679
+ if (toolChoice !== void 0) {
680
+ if (typeof toolChoice !== "string" || !XAI_SERVER_TOOL_CHOICES.has(toolChoice)) {
681
+ throw badXaiRequest(
682
+ `providerOptions.xai.toolChoice must be "auto", "required" or "none" for model "${model}".`
683
+ );
684
+ }
685
+ if (mapped.tools === void 0 || mapped.tools.length === 0) {
686
+ throw badXaiRequest(
687
+ `providerOptions.xai.toolChoice requires a non-empty providerOptions.xai.tools for model "${model}".`
688
+ );
689
+ }
690
+ mapped.toolChoice = toolChoice;
691
+ }
692
+ const maxTurns = xaiOpts["maxTurns"];
693
+ if (maxTurns !== void 0) {
694
+ if (typeof maxTurns !== "number" || !Number.isInteger(maxTurns) || maxTurns < 1) {
695
+ throw badXaiRequest(
696
+ `providerOptions.xai.maxTurns must be an integer >= 1 for model "${model}".`
697
+ );
698
+ }
699
+ if (mapped.tools === void 0 || mapped.tools.length === 0) {
700
+ throw badXaiRequest(
701
+ `providerOptions.xai.maxTurns requires a non-empty providerOptions.xai.tools for model "${model}".`
702
+ );
703
+ }
704
+ mapped.maxTurns = maxTurns;
705
+ }
578
706
  return mapped;
579
707
  }
580
708
  function parseXaiReplayState(value, model) {
@@ -944,6 +1072,7 @@ function xaiAdapter(opts) {
944
1072
  const structuredOutputRequested = req.outputJsonSchema !== void 0;
945
1073
  if (structuredOutputRequested) {
946
1074
  const schema = req.outputJsonSchema;
1075
+ assertXaiOutputJsonSchema(schema);
947
1076
  const name = isPlainRecord(schema) && typeof schema["title"] === "string" && schema["title"].length > 0 ? schema["title"] : "structured_output";
948
1077
  params.text = {
949
1078
  format: { type: "json_schema", name, schema, strict: true }
@@ -970,6 +1099,27 @@ function xaiAdapter(opts) {
970
1099
  );
971
1100
  }
972
1101
  params.tools = searchTools;
1102
+ if (xaiProviderConfig.toolChoice !== void 0) {
1103
+ if (req.toolChoice !== void 0) {
1104
+ throw badXaiRequest(
1105
+ `providerOptions.xai.toolChoice and toolChoice cannot both be set for model "${model}"; xAI accepts one tool_choice per request.`
1106
+ );
1107
+ }
1108
+ if (req.tools !== void 0 && req.tools.length > 0) {
1109
+ throw badXaiRequest(
1110
+ `providerOptions.xai.toolChoice applies to the server-side search tools only and cannot be combined with function tools for model "${model}".`
1111
+ );
1112
+ }
1113
+ if (hasFileRef) {
1114
+ throw badXaiRequest(
1115
+ `providerOptions.xai.toolChoice cannot be combined with file attachments for model "${model}"; xAI's implicit attachment_search would count as the tool call.`
1116
+ );
1117
+ }
1118
+ params.tool_choice = xaiProviderConfig.toolChoice;
1119
+ }
1120
+ if (xaiProviderConfig.maxTurns !== void 0) {
1121
+ params.max_turns = xaiProviderConfig.maxTurns;
1122
+ }
973
1123
  }
974
1124
  if (req.tools !== void 0 && req.tools.length > 0) {
975
1125
  if (req.modelDescriptor?.capabilities?.functionCalling !== true) {
@@ -1055,7 +1205,8 @@ function xaiAdapter(opts) {
1055
1205
  const finishReason = mapFinishReason(response);
1056
1206
  const expectedToolCounters = expectedServerToolCounters(
1057
1207
  xaiProviderConfig.tools);
1058
- if (expectedToolCounters.length > 0 || hasFileRef) {
1208
+ const noServerToolRan = response.usage["num_server_side_tools_used"] === 0 && response.usage["server_side_tool_usage_details"] === void 0;
1209
+ if ((expectedToolCounters.length > 0 || hasFileRef) && !noServerToolRan) {
1059
1210
  usage.details["server_tools_requested"] = 1;
1060
1211
  if (xaiProviderConfig.tools?.some((tool) => tool["type"] === "x_search") === true) {
1061
1212
  usage.details["x_search_requested"] = 1;