@algolia/wizard 0.103.0-rc.226.759 → 0.104.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/main.js CHANGED
@@ -1616,6 +1616,10 @@ async function clearWorkflowState(workflowId) {
1616
1616
  }
1617
1617
 
1618
1618
  // src/lib/modelProfiles.ts
1619
+ var MODEL_CAPABILITIES = {
1620
+ "claude-haiku-5-5": { forcedToolUse: true, lowestThinking: "disabled" },
1621
+ "claude-sonnet-5-5": { forcedToolUse: false, lowestThinking: "between_tools" }
1622
+ };
1619
1623
  var TOKENS_PER_MILLION = 1e6;
1620
1624
  var INITIAL_MODEL_ATTEMPT = 1;
1621
1625
  var NO_TOKENS = 0;
@@ -1627,57 +1631,57 @@ var IMPLEMENTATION_MAX_OUTPUT_TOKENS = 65536;
1627
1631
  var IMPLEMENTATION_MAX_STEPS = 48;
1628
1632
  var VALIDATION_MAX_OUTPUT_TOKENS = 8192;
1629
1633
  var VALIDATION_MAX_STEPS = 24;
1630
- var HAIKU_4_5_PRICES = {
1631
- input: 1,
1632
- output: 5,
1633
- cacheRead: 0.1,
1634
- cacheWrite: 1.25
1634
+ var HAIKU_5_5_PRICES = {
1635
+ input: 0.1,
1636
+ output: 0.5,
1637
+ cacheRead: 0.01,
1638
+ cacheWrite: 0.125
1635
1639
  };
1636
- var SONNET_5_PRICES = {
1640
+ var SONNET_5_5_PRICES = {
1637
1641
  input: 2,
1638
1642
  output: 10,
1639
- cacheRead: 0.2,
1643
+ cacheRead: 0.1,
1640
1644
  cacheWrite: 2.5
1641
1645
  };
1642
1646
  var MODEL_PROFILES = {
1643
1647
  quick: {
1644
- model: "claude-haiku-4-5",
1648
+ model: "claude-haiku-5-5",
1645
1649
  thinking: "disabled",
1646
1650
  maxOutputTokens: QUICK_MAX_OUTPUT_TOKENS,
1647
1651
  maxSteps: QUICK_MAX_STEPS,
1648
- prices: HAIKU_4_5_PRICES
1652
+ prices: HAIKU_5_5_PRICES
1649
1653
  },
1650
1654
  analysis: {
1651
- model: "claude-sonnet-5",
1655
+ model: "claude-sonnet-5-5",
1652
1656
  thinking: "adaptive",
1653
1657
  effort: "low",
1654
1658
  maxOutputTokens: ANALYSIS_MAX_OUTPUT_TOKENS,
1655
1659
  maxSteps: ANALYSIS_MAX_STEPS,
1656
- prices: SONNET_5_PRICES
1660
+ prices: SONNET_5_5_PRICES
1657
1661
  },
1658
1662
  implementation: {
1659
- model: "claude-sonnet-5",
1663
+ model: "claude-sonnet-5-5",
1660
1664
  thinking: "adaptive",
1661
1665
  effort: "medium",
1662
1666
  maxOutputTokens: IMPLEMENTATION_MAX_OUTPUT_TOKENS,
1663
1667
  maxSteps: IMPLEMENTATION_MAX_STEPS,
1664
- prices: SONNET_5_PRICES
1668
+ prices: SONNET_5_5_PRICES
1665
1669
  },
1666
1670
  implementationRetry: {
1667
- model: "claude-sonnet-5",
1671
+ model: "claude-sonnet-5-5",
1668
1672
  thinking: "adaptive",
1669
1673
  effort: "high",
1670
1674
  maxOutputTokens: IMPLEMENTATION_MAX_OUTPUT_TOKENS,
1671
1675
  maxSteps: IMPLEMENTATION_MAX_STEPS,
1672
- prices: SONNET_5_PRICES
1676
+ prices: SONNET_5_5_PRICES
1673
1677
  },
1674
1678
  validation: {
1675
- model: "claude-sonnet-5",
1679
+ model: "claude-sonnet-5-5",
1676
1680
  thinking: "adaptive",
1677
1681
  effort: "low",
1678
1682
  maxOutputTokens: VALIDATION_MAX_OUTPUT_TOKENS,
1679
1683
  maxSteps: VALIDATION_MAX_STEPS,
1680
- prices: SONNET_5_PRICES
1684
+ prices: SONNET_5_5_PRICES
1681
1685
  }
1682
1686
  };
1683
1687
  function getModelProfile(profileName) {
@@ -1690,6 +1694,14 @@ function providerOptionsForProfile(profileName) {
1690
1694
  ...profile.effort !== void 0 && { effort: profile.effort }
1691
1695
  };
1692
1696
  }
1697
+ function toolChoiceForProfile(profileName) {
1698
+ const { model } = getModelProfile(profileName);
1699
+ return MODEL_CAPABILITIES[model].forcedToolUse ? "required" : "auto";
1700
+ }
1701
+ function lowestThinkingForProfile(profileName) {
1702
+ const { model } = getModelProfile(profileName);
1703
+ return { type: MODEL_CAPABILITIES[model].lowestThinking };
1704
+ }
1693
1705
  function tokenCount(value = NO_TOKENS) {
1694
1706
  return value;
1695
1707
  }
@@ -1710,6 +1722,7 @@ function estimateModelCost(profileName, usage) {
1710
1722
  var COUNT_METRIC = 1;
1711
1723
  var GAUGE_METRIC = 3;
1712
1724
  var FIRST_ATTEMPT = 1;
1725
+ var NO_CONTINUATION = 0;
1713
1726
  var NO_TOKENS2 = 0;
1714
1727
  var NO_COST = 0;
1715
1728
  var AGENT_TELEMETRY_CONTEXT = {
@@ -1863,6 +1876,12 @@ function agentRunMetrics(event, values, tags) {
1863
1876
  type: COUNT_METRIC,
1864
1877
  tags: runTags
1865
1878
  },
1879
+ {
1880
+ name: "wizard.agent.continuation",
1881
+ value: Number((event.continuation ?? NO_CONTINUATION) > NO_CONTINUATION),
1882
+ type: COUNT_METRIC,
1883
+ tags: runTags
1884
+ },
1866
1885
  ...event.stepCount === void 0 ? [] : [
1867
1886
  {
1868
1887
  name: "wizard.agent.step_count",
@@ -1905,6 +1924,7 @@ function trackAgentRun(event) {
1905
1924
  effort: profile.effort ?? "none",
1906
1925
  thinking: profile.thinking,
1907
1926
  attempt: event.attempt,
1927
+ continuation: event.continuation,
1908
1928
  durationMs: event.durationMs,
1909
1929
  stepCount: event.stepCount,
1910
1930
  hitStepLimit: event.hitStepLimit,
@@ -2447,7 +2467,7 @@ async function ensureApplication(resumeAppId) {
2447
2467
  // package.json
2448
2468
  var package_default = {
2449
2469
  name: "@algolia/wizard",
2450
- version: "0.103.0-rc.226.759",
2470
+ version: "0.104.0",
2451
2471
  description: "Magically implement Algolia functionality in your codebase",
2452
2472
  type: "module",
2453
2473
  engines: {
@@ -2494,11 +2514,11 @@ var package_default = {
2494
2514
  vitest: "^4.1.11"
2495
2515
  },
2496
2516
  dependencies: {
2497
- "@ai-sdk/anthropic": "^3.0.118",
2498
- "@ai-sdk/openai-compatible": "^2.0.75",
2517
+ "@ai-sdk/anthropic": "^3.0.128",
2518
+ "@ai-sdk/openai-compatible": "^2.0.81",
2499
2519
  "@hono/node-server": "^2.0.10",
2500
2520
  "@segment/analytics-node": "^3.1.0",
2501
- ai: "^6.0.283",
2521
+ ai: "^6.0.302",
2502
2522
  "cross-keychain": "^1.1.0",
2503
2523
  dotenv: "^17.4.2",
2504
2524
  hono: "^4.13.8",
@@ -3196,6 +3216,18 @@ async function hasSymlinkParent(ctx, target) {
3196
3216
  }
3197
3217
  return false;
3198
3218
  }
3219
+ var fileLocks = /* @__PURE__ */ new Map();
3220
+ async function withFileLock(target, fn) {
3221
+ const previous = fileLocks.get(target) ?? Promise.resolve();
3222
+ const run2 = previous.then(fn);
3223
+ const settled = run2.catch(() => void 0);
3224
+ fileLocks.set(target, settled);
3225
+ try {
3226
+ return await run2;
3227
+ } finally {
3228
+ if (fileLocks.get(target) === settled) fileLocks.delete(target);
3229
+ }
3230
+ }
3199
3231
  function symlinkTargetError(target) {
3200
3232
  return `Refused: ${target} is a symbolic link.`;
3201
3233
  }
@@ -3444,7 +3476,10 @@ function writeFileTool(ctx) {
3444
3476
  return secretEnvWriteRefusal(filePath);
3445
3477
  }
3446
3478
  try {
3447
- const writeError = await safeWriteFile(ctx, resolved2.target, content);
3479
+ const writeError = await withFileLock(
3480
+ resolved2.target,
3481
+ () => safeWriteFile(ctx, resolved2.target, content)
3482
+ );
3448
3483
  if (writeError) return writeError;
3449
3484
  useWizard.getState().recordWrittenFile(resolved2.target);
3450
3485
  return `Wrote to ${filePath}`;
@@ -3510,29 +3545,31 @@ function editFileTool(ctx) {
3510
3545
  return "Error: oldString and newString are identical. No change made.";
3511
3546
  }
3512
3547
  try {
3513
- const unsafe = await validateSafeWriteTarget(ctx, resolved2.target);
3514
- if (unsafe) return unsafe;
3515
- let content;
3516
- try {
3517
- content = await readFile5(resolved2.target, "utf8");
3518
- } catch (err) {
3519
- if (err.code === "ENOENT") {
3520
- return `Error: ${filePath} does not exist. To create it, use writeFile.`;
3548
+ return await withFileLock(resolved2.target, async () => {
3549
+ const unsafe = await validateSafeWriteTarget(ctx, resolved2.target);
3550
+ if (unsafe) return unsafe;
3551
+ let content;
3552
+ try {
3553
+ content = await readFile5(resolved2.target, "utf8");
3554
+ } catch (err) {
3555
+ if (err.code === "ENOENT") {
3556
+ return `Error: ${filePath} does not exist. To create it, use writeFile.`;
3557
+ }
3558
+ throw err;
3521
3559
  }
3522
- throw err;
3523
- }
3524
- const matches = countOccurrences(content, oldString);
3525
- if (matches === 0) {
3526
- return `Error: oldString not found in ${filePath}. Read the file again and copy the exact text, including whitespace.`;
3527
- }
3528
- if (matches > 1 && !replaceAll) {
3529
- return `Error: oldString matches ${matches} places in ${filePath}. Add surrounding lines to make it unique, or set replaceAll to true.`;
3530
- }
3531
- const updated = replaceAll ? content.split(oldString).join(newString) : replaceFirst(content, oldString, newString);
3532
- const writeError = await safeWriteFile(ctx, resolved2.target, updated);
3533
- if (writeError) return writeError;
3534
- useWizard.getState().recordWrittenFile(resolved2.target);
3535
- return matches > 1 ? `Edited ${filePath} (${matches} replacements)` : `Edited ${filePath}. ${editedSnippet(updated, content.indexOf(oldString), newString)}`;
3560
+ const matches = countOccurrences(content, oldString);
3561
+ if (matches === 0) {
3562
+ return `Error: oldString not found in ${filePath}. Read the file again and copy the exact text, including whitespace.`;
3563
+ }
3564
+ if (matches > 1 && !replaceAll) {
3565
+ return `Error: oldString matches ${matches} places in ${filePath}. Add surrounding lines to make it unique, or set replaceAll to true.`;
3566
+ }
3567
+ const updated = replaceAll ? content.split(oldString).join(newString) : replaceFirst(content, oldString, newString);
3568
+ const writeError = await safeWriteFile(ctx, resolved2.target, updated);
3569
+ if (writeError) return writeError;
3570
+ useWizard.getState().recordWrittenFile(resolved2.target);
3571
+ return matches > 1 ? `Edited ${filePath} (${matches} replacements)` : `Edited ${filePath}. ${editedSnippet(updated, content.indexOf(oldString), newString)}`;
3572
+ });
3536
3573
  } catch (err) {
3537
3574
  return `Error editing ${filePath}: ${err.message}`;
3538
3575
  }
@@ -3872,82 +3909,87 @@ function writeCredentialsTool(ctx) {
3872
3909
  if (!targetIndex) {
3873
3910
  return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
3874
3911
  }
3875
- let existing = "";
3876
- let present;
3877
- try {
3878
- const targetError = await validateSafeWriteTarget(ctx, resolved2.target);
3879
- if (targetError) return targetError;
3912
+ return withFileLock(resolved2.target, async () => {
3913
+ let existing = "";
3914
+ let present;
3880
3915
  try {
3881
- existing = await readFile7(resolved2.target, "utf8");
3916
+ const targetError = await validateSafeWriteTarget(
3917
+ ctx,
3918
+ resolved2.target
3919
+ );
3920
+ if (targetError) return targetError;
3921
+ try {
3922
+ existing = await readFile7(resolved2.target, "utf8");
3923
+ } catch (err) {
3924
+ if (err.code !== "ENOENT") throw err;
3925
+ }
3926
+ present = [APP_ID_VAR, API_KEY_VAR].filter(
3927
+ (name) => readEnv(existing, name) !== null
3928
+ );
3882
3929
  } catch (err) {
3883
- if (err.code !== "ENOENT") throw err;
3930
+ return `Error writing credentials to ${filePath}: ${err.message}`;
3884
3931
  }
3885
- present = [APP_ID_VAR, API_KEY_VAR].filter(
3886
- (name) => readEnv(existing, name) !== null
3887
- );
3888
- } catch (err) {
3889
- return `Error writing credentials to ${filePath}: ${err.message}`;
3890
- }
3891
- const credentials = [];
3892
- const notes = [];
3893
- const fileAppId = readEnv(existing, APP_ID_VAR);
3894
- const fileKey = readEnv(existing, API_KEY_VAR);
3895
- const fileIndex = readEnv(existing, INDEX_NAME_VAR);
3896
- if (fileAppId === null || fileKey === null) {
3897
- try {
3898
- const selected = (await requireApplication(ctx.appId)).id;
3899
- if (fileAppId === null) credentials.push([APP_ID_VAR, selected]);
3900
- if (fileKey === null) {
3901
- if (fileAppId !== null && fileAppId !== selected) {
3932
+ const credentials = [];
3933
+ const notes = [];
3934
+ const fileAppId = readEnv(existing, APP_ID_VAR);
3935
+ const fileKey = readEnv(existing, API_KEY_VAR);
3936
+ const fileIndex = readEnv(existing, INDEX_NAME_VAR);
3937
+ if (fileAppId === null || fileKey === null) {
3938
+ try {
3939
+ const selected = (await requireApplication(ctx.appId)).id;
3940
+ if (fileAppId === null) credentials.push([APP_ID_VAR, selected]);
3941
+ if (fileKey === null) {
3942
+ if (fileAppId !== null && fileAppId !== selected) {
3943
+ notes.push(
3944
+ `No ${API_KEY_VAR} was provisioned: ${filePath} names application ${fileAppId}, but ${selected} is selected. Tell the user to remove ${APP_ID_VAR} from ${filePath} and re-run so a matching pair can be written, or to fill in a ${API_KEY_VAR} for ${fileAppId} by hand.`
3945
+ );
3946
+ } else {
3947
+ credentials.push([
3948
+ API_KEY_VAR,
3949
+ (await resolveWriteKey(targetIndex, selected)).key
3950
+ ]);
3951
+ }
3952
+ } else {
3902
3953
  notes.push(
3903
- `No ${API_KEY_VAR} was provisioned: ${filePath} names application ${fileAppId}, but ${selected} is selected. Tell the user to remove ${APP_ID_VAR} from ${filePath} and re-run so a matching pair can be written, or to fill in a ${API_KEY_VAR} for ${fileAppId} by hand.`
3954
+ `The existing ${API_KEY_VAR} must belong to application ${selected} or writes will fail.`
3904
3955
  );
3905
- } else {
3906
- credentials.push([
3907
- API_KEY_VAR,
3908
- (await resolveWriteKey(targetIndex, selected)).key
3909
- ]);
3910
3956
  }
3911
- } else {
3912
- notes.push(
3913
- `The existing ${API_KEY_VAR} must belong to application ${selected} or writes will fail.`
3914
- );
3957
+ } catch (err) {
3958
+ return `Error: could not resolve Algolia credentials (${err.message})`;
3915
3959
  }
3916
- } catch (err) {
3917
- return `Error: could not resolve Algolia credentials (${err.message})`;
3918
3960
  }
3919
- }
3920
- let wrote;
3921
- try {
3922
- const updated = [
3923
- ...credentials,
3924
- // Unconditional, unlike the credentials: the write key is scoped to
3925
- // the run's index, so a stale name left in the file earns a 403.
3926
- [INDEX_NAME_VAR, targetIndex]
3927
- ].reduce(
3928
- (content, [name, value]) => upsertEnv(content, name, value),
3929
- existing
3930
- );
3931
- const writeError = await safeWriteFile(ctx, resolved2.target, updated);
3932
- if (writeError) return writeError;
3933
- const sentences = [
3934
- `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
3935
- ];
3936
- if (fileIndex !== null && fileIndex !== targetIndex) {
3937
- sentences.push(
3938
- `That replaced the ${INDEX_NAME_VAR} already there ("${fileIndex}"), which this run does not target.`
3939
- );
3940
- }
3941
- if (present.length > 0) {
3942
- sentences.push(
3943
- `Skipped ${present.join(" and ")}: already defined there.`
3961
+ let wrote;
3962
+ try {
3963
+ const updated = [
3964
+ ...credentials,
3965
+ // Unconditional, unlike the credentials: the write key is scoped to
3966
+ // the run's index, so a stale name left in the file earns a 403.
3967
+ [INDEX_NAME_VAR, targetIndex]
3968
+ ].reduce(
3969
+ (content, [name, value]) => upsertEnv(content, name, value),
3970
+ existing
3944
3971
  );
3972
+ const writeError = await safeWriteFile(ctx, resolved2.target, updated);
3973
+ if (writeError) return writeError;
3974
+ const sentences = [
3975
+ `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
3976
+ ];
3977
+ if (fileIndex !== null && fileIndex !== targetIndex) {
3978
+ sentences.push(
3979
+ `That replaced the ${INDEX_NAME_VAR} already there ("${fileIndex}"), which this run does not target.`
3980
+ );
3981
+ }
3982
+ if (present.length > 0) {
3983
+ sentences.push(
3984
+ `Skipped ${present.join(" and ")}: already defined there.`
3985
+ );
3986
+ }
3987
+ wrote = [...sentences, ...notes].join(" ");
3988
+ } catch (err) {
3989
+ return `Error writing credentials to ${filePath}: ${err.message}`;
3945
3990
  }
3946
- wrote = [...sentences, ...notes].join(" ");
3947
- } catch (err) {
3948
- return `Error writing credentials to ${filePath}: ${err.message}`;
3949
- }
3950
- return wrote + await gitIgnoreOutcome(ctx, resolved2.target);
3991
+ return wrote + await gitIgnoreOutcome(ctx, resolved2.target);
3992
+ });
3951
3993
  }
3952
3994
  });
3953
3995
  }
@@ -5007,7 +5049,6 @@ async function classifyCommandSafety(createModel, command, cwd, explanation, app
5007
5049
  const { output, usage } = await generateText({
5008
5050
  model: anthropic(profile.model),
5009
5051
  maxOutputTokens: CLASSIFIER_MAX_OUTPUT_TOKENS,
5010
- temperature: 0,
5011
5052
  providerOptions: {
5012
5053
  anthropic: {
5013
5054
  thinking: modelOptions.thinking,
@@ -5418,6 +5459,10 @@ var MISSING_REPORT_STATUS_ERROR_MESSAGE = "Agent finished without calling report
5418
5459
  var MISSING_REPORT_USER_MESSAGE = "This step ran into a problem finishing. Run the wizard again to retry it.";
5419
5460
  var REPORT_STATUS_RETRIES = 2;
5420
5461
  var STEP_LIMIT_INSTRUCTION = "When reportStatus is the only tool available, you reached the step limit. This limit is not a failure, so do not report fail because of it: report status=success with the work done so far, and name the remaining work in the summary.";
5462
+ var TOOL_EVERY_TURN_INSTRUCTION = "Call at least one tool in every response. Do not reply with text only. The run ends only when you call reportStatus.";
5463
+ var ENDED_TURN_NUDGE = "Your last response did not call a tool. Continue the work with tools. When the work is done, call reportStatus.";
5464
+ var INITIAL_PROMPT = "Follow system instructions";
5465
+ var ENDED_TURN_CONTINUATIONS = 3;
5421
5466
  var RECENT_STEPS_LOGGED = 3;
5422
5467
  var LOGGED_CHARS = 300;
5423
5468
  var ATTEMPT_NUMBER_OFFSET = 1;
@@ -5455,6 +5500,13 @@ var MissingReportError = class extends Error {
5455
5500
  this.incompleteCause = incompleteCause;
5456
5501
  }
5457
5502
  };
5503
+ var EndedTurnError = class extends MissingReportError {
5504
+ continuation;
5505
+ constructor(continuation) {
5506
+ super(null);
5507
+ this.continuation = continuation;
5508
+ }
5509
+ };
5458
5510
  function incompleteCauseOf(stepCount, maxSteps, finishReason) {
5459
5511
  if (stepCount >= maxSteps) return "stepLimit";
5460
5512
  if (finishReason === "length") return "truncated";
@@ -5513,10 +5565,23 @@ function exhaustedError(kind, err) {
5513
5565
  }
5514
5566
  async function runAgent(req) {
5515
5567
  let retryReason = null;
5516
- for (let attempt = INITIAL_ATTEMPT; attempt <= REPORT_STATUS_RETRIES; attempt++) {
5568
+ let attempt = INITIAL_ATTEMPT;
5569
+ let continuation;
5570
+ let continuations = 0;
5571
+ for (; ; ) {
5517
5572
  try {
5518
- return await runAgentAttempt(req, attempt, retryReason);
5573
+ return await runAgentAttempt(req, attempt, retryReason, continuation);
5519
5574
  } catch (err) {
5575
+ continuation = void 0;
5576
+ if (err instanceof EndedTurnError && continuations < ENDED_TURN_CONTINUATIONS) {
5577
+ continuations++;
5578
+ continuation = err.continuation;
5579
+ logger.warn(
5580
+ { attempt: attempt + 1, continuations },
5581
+ "continuing runAgent after a turn ended without a tool call"
5582
+ );
5583
+ continue;
5584
+ }
5520
5585
  const kind = retryKind(err);
5521
5586
  if (!kind) throw err;
5522
5587
  if (attempt === REPORT_STATUS_RETRIES) throw exhaustedError(kind, err);
@@ -5525,19 +5590,23 @@ async function runAgent(req) {
5525
5590
  { attempt: attempt + 1, err },
5526
5591
  isMissingReportRetry(kind) ? "retrying runAgent after missing reportStatus" : "retrying runAgent after a transient provider error"
5527
5592
  );
5593
+ attempt++;
5528
5594
  }
5529
5595
  }
5530
- throw new Error("unreachable");
5531
5596
  }
5532
- async function runAgentAttempt(req, attempt, retryReason) {
5597
+ async function runAgentAttempt(req, attempt, retryReason, continuation) {
5533
5598
  const start = Date.now();
5534
5599
  const profileName = req.modelProfile ?? "implementation" /* implementation */;
5535
5600
  const profile = getModelProfile(profileName);
5536
5601
  const profileOptions = providerOptionsForProfile(profileName);
5602
+ const toolChoice = toolChoiceForProfile(profileName);
5537
5603
  const maxSteps = req.maxSteps ?? profile.maxSteps;
5604
+ const stepsUsed = continuation?.stepsUsed ?? 0;
5605
+ const stepsLeft = maxSteps - stepsUsed;
5538
5606
  const forceReport = retryReason === "missingReport";
5539
5607
  const incompleteReason = incompleteReasonOf(retryReason);
5540
- const modelOptions = forceReport ? { thinking: { type: "disabled" } } : profileOptions;
5608
+ const reportThinking = lowestThinkingForProfile(profileName);
5609
+ const modelOptions = forceReport ? { thinking: reportThinking, effort: void 0 } : profileOptions;
5541
5610
  logger.info(
5542
5611
  {
5543
5612
  startedAt: new Date(start).toISOString(),
@@ -5545,8 +5614,9 @@ async function runAgentAttempt(req, attempt, retryReason) {
5545
5614
  profile: profileName,
5546
5615
  model: profile.model,
5547
5616
  effort: forceReport ? void 0 : profile.effort,
5548
- thinking: forceReport ? "disabled" : profile.thinking,
5549
- retryReason
5617
+ thinking: forceReport ? reportThinking.type : profile.thinking,
5618
+ retryReason,
5619
+ stepsUsed
5550
5620
  },
5551
5621
  "runAgent started"
5552
5622
  );
@@ -5560,7 +5630,7 @@ async function runAgentAttempt(req, attempt, retryReason) {
5560
5630
  fetch: proxyFetch
5561
5631
  });
5562
5632
  const toolContext = req.toolContext ?? createToolContext();
5563
- if (attempt > INITIAL_ATTEMPT) resetToolCounts(toolContext);
5633
+ if (attempt > INITIAL_ATTEMPT && !continuation) resetToolCounts(toolContext);
5564
5634
  const readTools = ["readFile", "searchFiles", "listFiles"];
5565
5635
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
5566
5636
  const instructions = [
@@ -5569,6 +5639,7 @@ async function runAgentAttempt(req, attempt, retryReason) {
5569
5639
  "When you need to read or search multiple files, issue those tool calls together in one step rather than one at a time."
5570
5640
  ] : [],
5571
5641
  "Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use.",
5642
+ ...toolChoice === "auto" ? [TOOL_EVERY_TURN_INSTRUCTION] : [],
5572
5643
  STEP_LIMIT_INSTRUCTION,
5573
5644
  // Last so a retry does not bust the cached tools+system prefix from
5574
5645
  // the first attempt. Inspect-and-continue, not start over: implement
@@ -5613,11 +5684,11 @@ async function runAgentAttempt(req, attempt, retryReason) {
5613
5684
  output: req.outputSchema,
5614
5685
  tools: req.tools
5615
5686
  }),
5616
- toolChoice: "required",
5617
- stopWhen: [reportAccepted, stepCountIs(maxSteps)],
5687
+ toolChoice,
5688
+ stopWhen: [reportAccepted, stepCountIs(stepsLeft)],
5618
5689
  prepareStep: ({ stepNumber, messages }) => ({
5619
5690
  messages: withCachedHistory(messages),
5620
- ...stepNumber >= maxSteps - 1 && {
5691
+ ...stepNumber >= stepsLeft - 1 && {
5621
5692
  activeTools: ["reportStatus"]
5622
5693
  }
5623
5694
  }),
@@ -5625,9 +5696,11 @@ async function runAgentAttempt(req, attempt, retryReason) {
5625
5696
  ...{ onError: () => {
5626
5697
  } }
5627
5698
  });
5628
- const stream = await agent.stream({
5629
- prompt: "Follow system instructions"
5630
- });
5699
+ const prompt = continuation ? [
5700
+ ...continuation.messages,
5701
+ { role: "user", content: ENDED_TURN_NUDGE }
5702
+ ] : [{ role: "user", content: INITIAL_PROMPT }];
5703
+ const stream = await agent.stream({ prompt });
5631
5704
  let chunks = [];
5632
5705
  const chunkLimit = 5;
5633
5706
  let streamError;
@@ -5659,13 +5732,15 @@ async function runAgentAttempt(req, attempt, retryReason) {
5659
5732
  const usage = await stream.totalUsage;
5660
5733
  const steps = await stream.steps;
5661
5734
  const stepCount = steps.length;
5662
- const hitStepLimit = stepCount >= maxSteps;
5735
+ const totalSteps = stepsUsed + stepCount;
5736
+ const hitStepLimit = totalSteps >= maxSteps;
5663
5737
  const lastStep = steps[steps.length - 1];
5664
5738
  const finishReason = lastStep?.finishReason;
5665
5739
  trackAgentRun({
5666
5740
  operation: req.operation,
5667
5741
  profile: profileName,
5668
5742
  attempt: attempt + ATTEMPT_NUMBER_OFFSET,
5743
+ continuation: continuation?.number ?? 0,
5669
5744
  durationMs: end - start,
5670
5745
  usage,
5671
5746
  stepCount,
@@ -5681,6 +5756,7 @@ async function runAgentAttempt(req, attempt, retryReason) {
5681
5756
  model: profile.model,
5682
5757
  usage,
5683
5758
  stepCount,
5759
+ totalSteps,
5684
5760
  maxSteps,
5685
5761
  hitStepLimit,
5686
5762
  finishReason
@@ -5707,8 +5783,17 @@ async function runAgentAttempt(req, attempt, retryReason) {
5707
5783
  },
5708
5784
  "Agent finished without calling reportStatus"
5709
5785
  );
5786
+ const endedTurn = toolChoice === "auto" && finishReason === "stop" && !lastStep?.toolCalls?.length && !hitStepLimit;
5787
+ if (endedTurn) {
5788
+ const { messages } = await stream.response;
5789
+ throw new EndedTurnError({
5790
+ messages: [...prompt, ...messages],
5791
+ stepsUsed: totalSteps,
5792
+ number: (continuation?.number ?? 0) + 1
5793
+ });
5794
+ }
5710
5795
  throw new MissingReportError(
5711
- incompleteCauseOf(stepCount, maxSteps, finishReason)
5796
+ incompleteCauseOf(totalSteps, maxSteps, finishReason)
5712
5797
  );
5713
5798
  }
5714
5799
  const result = report.output;
@@ -6682,6 +6767,7 @@ function searchInstructions(input) {
6682
6767
  "Style the search surfaces through the widget's class and CSS-variable overrides (or, when hand-built, the app's existing theme tokens): give the results panel and the element that actually draws the input's border and background \u2014 for a widget, the form or wrapper it renders around the bare input \u2014 non-transparent surfaces, keep that frame distinguishable from the background behind it in every supported theme, and never assume a light surface or reuse the surrounding page's colors unchanged inside the panel.",
6683
6768
  "The panel takes its width from the input by default, so a small input makes it unreadably narrow: give it `min-width: min(320px, calc(100vw - 2rem))` independent of the input, cap its maximum width to the viewport, and anchor it to the input edge it opens from so widening does not push it off-screen.",
6684
6769
  "Match the application styles closely, including light, dark, and high-contrast themes the project already supports.",
6770
+ "Give the search input frame the same height, corner radius, border color and width, background, and focus style as the input it replaces. When it replaces nothing, copy them from the form controls beside it. Read these values from their source (the theme, the component props, or the CSS). Do not keep the widget theme defaults, for example a 44px height and 4px corners, and do not invent colors. When the app theme exists only in code, take the values from that theme object.",
6685
6771
  "When you add UI text to a localized dictionary or message file, add the same keys, translated, to every locale the project supports.",
6686
6772
  "Before reporting, mount the component. Do not run a type check, build, or test command unless a later instruction allows it: the validation pass runs the type check, after the developer agrees to it. If a tool limit is reached or an error remains unresolved, keep the implementation, report status=success, and name the remaining issue in the summary so validation can drive the next pass. Report status=fail only when no search implementation can be written, with the concrete blocker as the reason.",
6687
6773
  "The summary should be extremely concise; do not mention manual testing steps."
@@ -6788,16 +6874,18 @@ function validationInstructions(input) {
6788
6874
  `The confirmed placement candidate was "${input.searchLocation ?? "unknown"}", but verify the actual mount rather than assuming the candidate was used.`,
6789
6875
  "Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on type checks or tests.",
6790
6876
  "Confirm that the application renders exactly one search bar. Check for an old search bar and duplicate mounts.",
6791
- "If the project sets a Content-Security-Policy, confirm that connect-src allows https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io, and that search-insights is loaded from npm (not a CDN). If a CSP exists and fails either check, set sufficient=false. If the project has no CSP, skip both checks: do not set sufficient=false, and do not ask to add a CSP.",
6877
+ "If the project sets a Content-Security-Policy, confirm that connect-src allows https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io, and that search-insights is loaded from npm (not a CDN). If a CSP exists and fails either check, set sufficient=false. If the project has no CSP, skip both checks: do not set sufficient=false, and do not ask to add a CSP. Without a CSP, insights={true} (or insights: true) with no search-insights package is correct, because the widget loads search-insights from its CDN: do not report a missing search-insights package.",
6792
6878
  "Confirm content-specific search is placed with the corresponding content or task controls. Shared navigation is valid only for genuinely app-wide search that fits the visual hierarchy and responsive layout without crowding primary actions.",
6793
6879
  "Confirm the input has a persistent accessible name; widget-provided combobox, popup, and item relationships and keyboard behavior are preserved; and loading, empty, and error status text is perceivable to assistive technology.",
6794
6880
  "Inspect the CSS that actually applies to the generated DOM. Confirm the results panel and the element drawing the input frame have non-transparent surfaces, the input frame is distinguishable from its surrounding background in every supported theme, text meets 4.5:1 contrast, meaningful boundaries and focus indicators meet 3:1, and no state relies on color alone.",
6795
6881
  "Confirm the search experience reflows without horizontal page scrolling at 320 CSS pixels, keeps its panel on-screen, preserves visible focus, and respects reduced-motion preferences for any added animation.",
6796
6882
  "If the project imports an InstantSearch theme, confirm that the project sets the theme palette to the app colors in each theme that the app supports: --ais-primary-color-rgb to the app accent color, and --ais-background-color-rgb, --ais-text-color-rgb, and --ais-border-color-rgb to the app surface, text, and border colors. If the project keeps the theme defaults, set sufficient=false, because the search icon, focus ring, and active item then show the theme blue, and the panel shows the theme surface instead of the app surface. A match between the light and dark themes of the widget and the page is not enough. The theme colors .ais-Highlight-highlighted and .ais-Snippet-highlighted with a fixed blue that the palette does not change: if the results show either class and the project does not restyle it, set sufficient=false.",
6797
6883
  "Then confirm that the palette overrides (--ais-*-rgb) win over the theme: they must use a higher specificity than the theme, for example `:root:root`, and for a dark mode `:root:root.dark` or `:root:root[data-theme=dark]`, because the theme sets its own blue in `:root` and in `:root[data-theme=dark], .dark`. `:root:root` alone ties with `:root[data-theme=dark]`. If an override can lose to the theme, set sufficient=false, because the search icon and focus ring then show the theme blue instead of the app accent color.",
6798
- "Find how the app selects each theme it supports (a class or attribute on an ancestor, a prefers-color-scheme media query, or a theme that exists only in code). Confirm that the search input and results panel take their colors from the active app theme through that same switch. A search widget whose colors do not match the page theme, for example a light panel on a dark page, is a failure: set sufficient=false.",
6884
+ "Find how the app selects each theme it supports (a class or attribute on an ancestor, a prefers-color-scheme media query, or a theme that exists only in code). A theme is supported only when the app can select it at runtime: a mounted theme switch, or a stored or system preference that the app applies. A theme file or theme option that the app never selects, for example because the app forces one mode, does not count. Confirm that the search input and results panel take their colors from the active app theme through that same switch. A search widget whose colors do not match the page theme, for example a light panel on a dark page, is a failure: set sufficient=false.",
6799
6885
  "When the project has a package.json, confirm that the theme CSS of the search widget (for InstantSearch, a theme from instantsearch.css) is imported once from a legal global-CSS entry for that framework (Next.js Pages Router: pages/_app; Next.js App Router: root layout; otherwise the app global stylesheet or a component module when allowed). If it is installed but not imported, or imported from a Next.js component file, set sufficient=false.",
6800
6886
  "Confirm that the search component is reachable from and fits the corresponding content task at every supported breakpoint; require app-wide reachability only when the search scope is genuinely app-wide.",
6887
+ "For an InstantSearch Autocomplete, confirm that the item component (the React itemComponent, or the item template in indices[].templates) calls the onSelect that it receives when the user clicks the result, for example onClick={onSelect} on its root element. The widget goes to the getURL route only through onSelect: the click on its <li> only sends the Insights event. If the item ignores onSelect, set sufficient=false, because a click on a result then does nothing.",
6888
+ "Confirm that the search input frame has the same height, corner radius, border, and background as the input that it replaces, or as the form controls beside it when it replaces nothing. If the frame keeps the widget theme defaults or uses colors that the app does not use, set sufficient=false.",
6801
6889
  "Do not run the test suite or any test command.",
6802
6890
  "Do not modify files. Report each implementation issue as a concrete instruction for the next implementation pass.",
6803
6891
  `Detected verification tools: ${JSON.stringify(input.findings.verification ?? [])}. Use only a type checker; ignore any linter or formatter in this list.`,
@@ -6812,7 +6900,7 @@ function validationInstructions(input) {
6812
6900
  "Set sufficient=false when a required placement, accessibility, or theme-color property is observed to fail, the implementation is incomplete, or checks have implementation-caused failures; include concrete additionalInstructions for the next pass.",
6813
6901
  "You can only read source files. If a required property can be checked in the source, check it there. If it can be established only in a browser (rendered contrast, keyboard behavior, 320-pixel reflow, live-region status, computed accessible name), set sufficient=true when the source shows no violation. Never request devtools, a screenshot, or a contrast tool, and never repeat a finding that an earlier pass already fixed.",
6814
6902
  "If a check fails only in untouched code, set unrelatedFailure to a concise failure description.",
6815
- "Set styleOnly=true when sufficient=false and every finding is one of these: the theme palette defaults remain, a palette override loses to the theme, or the highlight classes keep the theme blue. If any other finding exists, for example a light panel on a dark page, do not set styleOnly.",
6903
+ "Set styleOnly=true when sufficient=false and every finding is one of these: the theme palette defaults remain, a palette override loses to the theme, the highlight classes keep the theme blue, or the input frame does not match the size, radius, border, or background of the controls that it must match. If any other finding exists, for example a light panel on a dark page or a result that a click does not open, do not set styleOnly.",
6816
6904
  "Keep the summary to one short sentence. Put the details in additionalInstructions."
6817
6905
  ];
6818
6906
  }
@@ -7116,10 +7204,17 @@ function validationRunInstructions(plan, attempt) {
7116
7204
  "Do not use an autofix option during validation."
7117
7205
  ];
7118
7206
  }
7207
+ function previousFindingsInstructions(findings) {
7208
+ if (!findings) return [];
7209
+ return [
7210
+ `The previous validation pass reported these findings: ${findings}`,
7211
+ "Check those findings first. Keep the conclusions of that pass that the changes did not affect, for example which themes the app supports. Report a new finding only when it breaks the search (the search bar does not render, search returns no results, or a click on a result does not open it), fails an accessibility requirement, or comes from a change made after the previous pass. Do not set sufficient=false for any other new issue: name it briefly in the summary."
7212
+ ];
7213
+ }
7119
7214
  function retryTypeCheckInstructions(plan) {
7120
7215
  if (!plan.run) return [];
7121
7216
  return [
7122
- "The developer approved the type check. Run it once with the project's existing command right after you apply the fixes, before other work. Do not install dependencies, use npx, dlx, or another package runner, or use an autofix option. Fix the errors that your changes cause, and name any error that remains in the summary."
7217
+ "The developer approved the type check. Run it once with the project's existing command right after you apply the fixes, before other work. Do not install dependencies unless a finding requires a package upgrade. If it does, install that upgrade once with the project's package manager, then run the type check. Do not use npx, dlx, or another package runner, or an autofix option. Fix the errors that your changes cause, and name any error that remains in the summary."
7123
7218
  ];
7124
7219
  }
7125
7220
  async function resolveIngestionSource(ctx, repoRoot) {
@@ -7559,6 +7654,7 @@ ${detail}` : ""}`
7559
7654
  );
7560
7655
  let extraInstructions = [];
7561
7656
  let buildCheckPlan;
7657
+ let previousFindings;
7562
7658
  const confirmedFrameworks = language.frameworks ?? [];
7563
7659
  const detectedFrameworks = scan.frameworks ?? [];
7564
7660
  const framework = confirmedFrameworks[0]?.name ?? "unknown";
@@ -7604,9 +7700,10 @@ ${detail}` : ""}`
7604
7700
  searchConfigFile = searchResult.searchConfigFile;
7605
7701
  }
7606
7702
  buildCheckPlan ??= await resolveBuildCheckPlan(ctx, repoRoot, input);
7607
- const validation = await runValidationUseCase(
7608
- validationRunInstructions(buildCheckPlan, attempt)
7609
- );
7703
+ const validation = await runValidationUseCase([
7704
+ ...validationRunInstructions(buildCheckPlan, attempt),
7705
+ ...previousFindingsInstructions(previousFindings)
7706
+ ]);
7610
7707
  summaries.push(formatSummary("validation", validation.summary));
7611
7708
  const finalAttempt = attempt === MAX_IMPLEMENT_VALIDATION_ATTEMPTS;
7612
7709
  let investigateUnrelated = false;
@@ -7657,6 +7754,7 @@ ${detail}` : ""}`
7657
7754
  `Unable to validate the implementation after ${MAX_IMPLEMENT_VALIDATION_ATTEMPTS} attempts (details in wizard.log): ${briefSummary(validation.summary)}`
7658
7755
  );
7659
7756
  }
7757
+ previousFindings = validation.sufficient ? void 0 : validation.additionalInstructions ?? validation.summary;
7660
7758
  extraInstructions = validation.sufficient ? [] : validationRetryInstructions(validation);
7661
7759
  if (validation.unrelatedFailure && investigateUnrelated) {
7662
7760
  extraInstructions.push(
@@ -10082,7 +10180,7 @@ async function resetProjectState() {
10082
10180
  // package.json with { type: 'json' }
10083
10181
  var package_default2 = {
10084
10182
  name: "@algolia/wizard",
10085
- version: "0.103.0-rc.226.759",
10183
+ version: "0.104.0",
10086
10184
  description: "Magically implement Algolia functionality in your codebase",
10087
10185
  type: "module",
10088
10186
  engines: {
@@ -10129,11 +10227,11 @@ var package_default2 = {
10129
10227
  vitest: "^4.1.11"
10130
10228
  },
10131
10229
  dependencies: {
10132
- "@ai-sdk/anthropic": "^3.0.118",
10133
- "@ai-sdk/openai-compatible": "^2.0.75",
10230
+ "@ai-sdk/anthropic": "^3.0.128",
10231
+ "@ai-sdk/openai-compatible": "^2.0.81",
10134
10232
  "@hono/node-server": "^2.0.10",
10135
10233
  "@segment/analytics-node": "^3.1.0",
10136
- ai: "^6.0.283",
10234
+ ai: "^6.0.302",
10137
10235
  "cross-keychain": "^1.1.0",
10138
10236
  dotenv: "^17.4.2",
10139
10237
  hono: "^4.13.8",
@@ -87,14 +87,15 @@ Ensure the attributes you map into the component match the entity. 'title' above
87
87
  `searchParameters` (for example `['body:20']`), or as an index setting. Do both
88
88
  for the same attribute names the template passes to Snippet.
89
89
 
90
- ### Navigation is `getURL`'s job, not the template's
90
+ ### Navigation: `getURL` plus the template's `onSelect`
91
91
 
92
92
  - `indices[].getURL` — `(item) => string`, a route that already exists; don't invent one.
93
- It navigates on click **and** on Enter over a highlighted item, and records the click
94
- event.
95
- - The `item` template renders content only: **no `<a>`, no `href`, no click handler of
96
- your own, never `stopPropagation`.** The widget's `<li>` owns the click — it sends the
97
- Insights `click`, closes the panel, clears the active option.
93
+ The widget goes there when `onSelect` runs: on Enter over a highlighted item, and on a
94
+ click only when the `item` template calls `onSelect`.
95
+ - The `item` template **must call the `onSelect` it is handed on click**, for example
96
+ `` html`<div onClick=${onSelect}>…</div>` ``. The widget's `<li>` click only sends the
97
+ Insights `click`. Without `onSelect`, a click on a result does nothing. No `<a>`, no
98
+ `href`, no other click handler, never `stopPropagation`.
98
99
  - Do not pass a top-level `onSelect` to `autocomplete()` to navigate. `getURL` already
99
100
  navigates.
100
101
  - `getSearchPageURL` sends Enter with nothing highlighted to a full results page; pass it
@@ -287,8 +288,8 @@ insights: {
287
288
 
288
289
  With `insights: true`, `autocomplete` sends `view` (on render) and `click` (from the
289
290
  result element's own handler) itself. Its item template gets `{ item, onSelect }` and
290
- **no `sendEvent`** — nothing to wire. Call `onSelect` for the widget's selection behavior
291
- (close the panel, record a recent search); the click event fires either way.
291
+ **no `sendEvent`** — nothing to wire. Call `onSelect` on click: it goes to
292
+ the `getURL` route and closes the panel. The click event fires either way.
292
293
 
293
294
  `hits` and `infiniteHits` behave the same; only a custom `item` template whose click
294
295
  target is your own element needs wiring — it gets `sendEvent` as the second argument.
@@ -25,8 +25,11 @@ dismissal, Up/Down navigation with wrap-around, Enter/Escape/Tab, and the combob
25
25
  (`combobox` on the input, `aria-expanded`, `aria-activedescendant`) — add no open/close
26
26
  logic or ARIA roles of your own.
27
27
 
28
- Needs `react-instantsearch` **7.18.0+**. If an older 7.x is pinned, upgrade within `^7`
29
- rather than switching widgets.
28
+ Needs `react-instantsearch` **7.41.0+**. That is the first release that exports
29
+ `Autocomplete`. Earlier 7.x only exports `EXPERIMENTAL_Autocomplete`, which fails
30
+ the type check. If an older 7.x is pinned, upgrade to the latest 7.x (`^7`) and
31
+ install it before importing `Autocomplete`. Do not import
32
+ `EXPERIMENTAL_Autocomplete`, and do not switch widgets.
30
33
 
31
34
  ```tsx
32
35
  import { liteClient as algoliasearch } from 'algoliasearch/lite'
@@ -79,14 +82,15 @@ Ensure the attributes you map into the component match the entity. 'title' above
79
82
  `searchParameters` (for example `['body:20']`), or as an index setting. Do both
80
83
  for the same attribute names the component passes to Snippet.
81
84
 
82
- ### Navigation is `getURL`'s job, not the item component's
85
+ ### Navigation: `getURL` plus the item's `onSelect`
83
86
 
84
87
  - `indices[].getURL` — `(item) => string`, a route that already exists; don't invent one.
85
- It navigates on click **and** on Enter over a highlighted item, and records the click
86
- event.
87
- - `itemComponent` renders content only: **no `<a>`, no `href`, no `onClick` beyond the
88
- `onSelect` it is handed, never `stopPropagation`.** The widget's `<li>` owns the click —
89
- it sends the Insights `click`, closes the panel, clears the active option.
88
+ The widget goes there when `onSelect` runs: on Enter over a highlighted item, and on a
89
+ click only when the item component calls `onSelect`.
90
+ - `itemComponent` **must call the `onSelect` it is handed on click**, for example
91
+ `<div onClick={onSelect}>`. The widget's `<li>` click only sends the Insights `click`.
92
+ Without `onSelect`, a click on a result does nothing. No `<a>`, no `href`, no other
93
+ `onClick`, never `stopPropagation`.
90
94
  - Do not pass a top-level `onSelect` to `<Autocomplete>` (for example to call
91
95
  `router.push`). `getURL` already navigates. The top-level callback can receive a
92
96
  `SyntheticEvent` instead of `{ item }`, so reading `item` from it fails the type check.
@@ -281,8 +285,8 @@ policy allows it:
281
285
  With `insights={true}`, `<Autocomplete>` sends `view` (on render) and `click` (from the
282
286
  result element's own handler) itself. Its `itemComponent` receives
283
287
  `{ item, onSelect, onApply }` and **no `sendEvent`** — nothing to wire. Call `onSelect`
284
- for the widget's selection behavior (close the panel, record a recent search); the click
285
- event fires either way.
288
+ on click: it goes to the `getURL` route and closes the panel. The click event fires
289
+ either way.
286
290
 
287
291
  `<Hits>` and `<InfiniteHits>` behave the same way. Two cases there need explicit wiring:
288
292
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.103.0-rc.226.759",
3
+ "version": "0.104.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {
@@ -47,11 +47,11 @@
47
47
  "vitest": "^4.1.11"
48
48
  },
49
49
  "dependencies": {
50
- "@ai-sdk/anthropic": "^3.0.118",
51
- "@ai-sdk/openai-compatible": "^2.0.75",
50
+ "@ai-sdk/anthropic": "^3.0.128",
51
+ "@ai-sdk/openai-compatible": "^2.0.81",
52
52
  "@hono/node-server": "^2.0.10",
53
53
  "@segment/analytics-node": "^3.1.0",
54
- "ai": "^6.0.283",
54
+ "ai": "^6.0.302",
55
55
  "cross-keychain": "^1.1.0",
56
56
  "dotenv": "^17.4.2",
57
57
  "hono": "^4.13.8",