@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
|
|
1631
|
-
input: 1,
|
|
1632
|
-
output: 5,
|
|
1633
|
-
cacheRead: 0.
|
|
1634
|
-
cacheWrite:
|
|
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
|
|
1640
|
+
var SONNET_5_5_PRICES = {
|
|
1637
1641
|
input: 2,
|
|
1638
1642
|
output: 10,
|
|
1639
|
-
cacheRead: 0.
|
|
1643
|
+
cacheRead: 0.1,
|
|
1640
1644
|
cacheWrite: 2.5
|
|
1641
1645
|
};
|
|
1642
1646
|
var MODEL_PROFILES = {
|
|
1643
1647
|
quick: {
|
|
1644
|
-
model: "claude-haiku-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
2498
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
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.
|
|
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
|
|
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
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
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
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3930
|
+
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
3884
3931
|
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
);
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
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
|
-
`
|
|
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
|
-
}
|
|
3912
|
-
|
|
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
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
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
|
|
3947
|
-
}
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
5617
|
-
stopWhen: [reportAccepted, stepCountIs(
|
|
5687
|
+
toolChoice,
|
|
5688
|
+
stopWhen: [reportAccepted, stepCountIs(stepsLeft)],
|
|
5618
5689
|
prepareStep: ({ stepNumber, messages }) => ({
|
|
5619
5690
|
messages: withCachedHistory(messages),
|
|
5620
|
-
...stepNumber >=
|
|
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
|
|
5629
|
-
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
10133
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
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.
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
- The `item` template
|
|
96
|
-
|
|
97
|
-
Insights `click`,
|
|
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`
|
|
291
|
-
|
|
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.
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
- `itemComponent`
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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.
|
|
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.
|
|
51
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
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.
|
|
54
|
+
"ai": "^6.0.302",
|
|
55
55
|
"cross-keychain": "^1.1.0",
|
|
56
56
|
"dotenv": "^17.4.2",
|
|
57
57
|
"hono": "^4.13.8",
|