mixdog 0.9.46 → 0.9.49
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/package.json +14 -4
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +647 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/defaults/cycle3-review-prompt.md +10 -4
- package/src/defaults/memory-promote-prompt.md +4 -3
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +13 -5
- package/src/runtime/memory/lib/memory-cycle2.mjs +8 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +41 -6
- package/src/runtime/memory/lib/query-handlers.mjs +49 -6
- package/src/runtime/memory/lib/recall-format.mjs +21 -6
- package/src/runtime/memory/tool-defs.mjs +2 -3
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export const PATCH_PLATFORMS = {
|
|
6
|
+
'darwin-arm64': 'mixdog-patch-darwin-arm64',
|
|
7
|
+
'darwin-x64': 'mixdog-patch-darwin-x64',
|
|
8
|
+
'linux-arm64': 'mixdog-patch-linux-arm64',
|
|
9
|
+
'linux-x64': 'mixdog-patch-linux-x64',
|
|
10
|
+
'win32-x64': 'mixdog-patch-win32-x64.exe',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const GRAPH_PLATFORMS = {
|
|
14
|
+
'darwin-arm64': 'mixdog-graph-darwin-arm64',
|
|
15
|
+
'darwin-x64': 'mixdog-graph-darwin-x64',
|
|
16
|
+
'linux-arm64': 'mixdog-graph-linux-arm64',
|
|
17
|
+
'linux-x64': 'mixdog-graph-linux-x64',
|
|
18
|
+
'win32-x64': 'mixdog-graph-win32-x64.exe',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const RUNTIME_PLATFORMS = [
|
|
22
|
+
'linux-x64',
|
|
23
|
+
'linux-arm64',
|
|
24
|
+
'darwin-x64',
|
|
25
|
+
'darwin-arm64',
|
|
26
|
+
'win32-x64',
|
|
27
|
+
];
|
|
28
|
+
const STRICT_VERSION = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
29
|
+
const SHA256 = /^[a-f0-9]{64}$/i;
|
|
30
|
+
export const MAX_ASSET_BYTES = 256 * 1024 * 1024;
|
|
31
|
+
export const MAX_DOWNLOAD_TIMEOUT_MS = 300_000;
|
|
32
|
+
const PATCH_MANIFEST_PATH = 'src/runtime/agent/orchestrator/tools/patch-manifest.json';
|
|
33
|
+
const PATCH_CARGO_PATH = 'native/mixdog-patch/Cargo.toml';
|
|
34
|
+
const RUNTIME_MANIFEST_PATH = 'src/runtime/memory/data/runtime-manifest.json';
|
|
35
|
+
const GRAPH_MANIFEST_PATH = 'src/runtime/agent/orchestrator/tools/graph-manifest.json';
|
|
36
|
+
const PACKAGE_PATH = 'package.json';
|
|
37
|
+
|
|
38
|
+
function assertPlainObject(value, label) {
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
40
|
+
throw new Error(`${label} must be an object`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertExactKeys(value, expected, label) {
|
|
45
|
+
const actual = Object.keys(value).sort();
|
|
46
|
+
const wanted = [...expected].sort();
|
|
47
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
48
|
+
throw new Error(`${label} keys must be exactly: ${wanted.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function validatePatchManifest(manifest, cargoToml) {
|
|
53
|
+
assertPlainObject(manifest, 'Patch manifest');
|
|
54
|
+
assertExactKeys(manifest, ['version', '_comment', 'assets'], 'Patch manifest');
|
|
55
|
+
if (typeof manifest.version !== 'string' || !STRICT_VERSION.test(manifest.version)) {
|
|
56
|
+
throw new Error(`Patch manifest version is not strict MAJOR.MINOR.PATCH: ${manifest.version}`);
|
|
57
|
+
}
|
|
58
|
+
if (typeof manifest._comment !== 'string' || !manifest._comment) {
|
|
59
|
+
throw new Error('Patch manifest _comment must be a non-empty string');
|
|
60
|
+
}
|
|
61
|
+
assertPlainObject(manifest.assets, 'Patch manifest assets');
|
|
62
|
+
assertExactKeys(manifest.assets, Object.keys(PATCH_PLATFORMS), 'Patch manifest assets');
|
|
63
|
+
|
|
64
|
+
let inPackage = false;
|
|
65
|
+
let cargoVersion;
|
|
66
|
+
for (const line of String(cargoToml).split(/\r?\n/)) {
|
|
67
|
+
const section = line.match(/^\s*\[([^\]]+)\]\s*$/)?.[1];
|
|
68
|
+
if (section) {
|
|
69
|
+
inPackage = section === 'package';
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (inPackage) {
|
|
73
|
+
const version = line.match(/^\s*version\s*=\s*"([^"]+)"\s*$/)?.[1];
|
|
74
|
+
if (version) {
|
|
75
|
+
cargoVersion = version;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!cargoVersion) throw new Error('Could not read [package] version from native/mixdog-patch/Cargo.toml');
|
|
81
|
+
if (cargoVersion !== manifest.version) {
|
|
82
|
+
throw new Error(`Patch Cargo version ${cargoVersion} does not match manifest version ${manifest.version}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const [platform, filename] of Object.entries(PATCH_PLATFORMS)) {
|
|
86
|
+
const asset = manifest.assets[platform];
|
|
87
|
+
assertPlainObject(asset, `Patch asset ${platform}`);
|
|
88
|
+
assertExactKeys(asset, ['url', 'sha256'], `Patch asset ${platform}`);
|
|
89
|
+
if (typeof asset.url !== 'string') throw new Error(`${platform}: patch asset URL must be a string`);
|
|
90
|
+
let url;
|
|
91
|
+
try {
|
|
92
|
+
url = new URL(asset.url);
|
|
93
|
+
} catch {
|
|
94
|
+
throw new Error(`${platform}: invalid patch asset URL`);
|
|
95
|
+
}
|
|
96
|
+
const expectedPath = `/tribgames/mixdog/releases/download/patch-v${manifest.version}/${filename}`;
|
|
97
|
+
if (
|
|
98
|
+
url.protocol !== 'https:'
|
|
99
|
+
|| url.hostname !== 'github.com'
|
|
100
|
+
|| url.port
|
|
101
|
+
|| url.username
|
|
102
|
+
|| url.password
|
|
103
|
+
|| url.search
|
|
104
|
+
|| url.hash
|
|
105
|
+
|| url.pathname !== expectedPath
|
|
106
|
+
) {
|
|
107
|
+
throw new Error(`${platform}: patch asset URL must be https://github.com${expectedPath}`);
|
|
108
|
+
}
|
|
109
|
+
if (typeof asset.sha256 !== 'string' || !SHA256.test(asset.sha256)) {
|
|
110
|
+
throw new Error(`${platform}: invalid patch asset sha256`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return manifest;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function validateRuntimeManifest(manifest) {
|
|
117
|
+
assertPlainObject(manifest, 'Runtime manifest');
|
|
118
|
+
if (!manifest.release_tag || !manifest.assets || typeof manifest.assets !== 'object') {
|
|
119
|
+
throw new Error(`${RUNTIME_MANIFEST_PATH} is missing release_tag or assets`);
|
|
120
|
+
}
|
|
121
|
+
assertPlainObject(manifest.assets, 'Runtime manifest assets');
|
|
122
|
+
assertExactKeys(manifest.assets, RUNTIME_PLATFORMS, 'Runtime manifest assets');
|
|
123
|
+
for (const [platform, asset] of Object.entries(manifest.assets)) {
|
|
124
|
+
if (typeof asset?.url !== 'string' || new URL(asset.url).protocol !== 'https:') {
|
|
125
|
+
throw new Error(`${platform}: invalid HTTPS asset URL`);
|
|
126
|
+
}
|
|
127
|
+
if (typeof asset.sha256 !== 'string' || !SHA256.test(asset.sha256)) {
|
|
128
|
+
throw new Error(`${platform}: invalid sha256`);
|
|
129
|
+
}
|
|
130
|
+
if (!Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > MAX_ASSET_BYTES) {
|
|
131
|
+
throw new Error(`${platform}: invalid size`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return manifest;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function validateGraphManifest(manifest, packageJson) {
|
|
138
|
+
assertPlainObject(manifest, 'Graph manifest');
|
|
139
|
+
assertExactKeys(manifest, ['version', '_comment', 'assets'], 'Graph manifest');
|
|
140
|
+
if (typeof manifest.version !== 'string' || !STRICT_VERSION.test(manifest.version)) {
|
|
141
|
+
throw new Error(`Graph manifest version is not strict MAJOR.MINOR.PATCH: ${manifest.version}`);
|
|
142
|
+
}
|
|
143
|
+
if (typeof manifest._comment !== 'string' || !manifest._comment) {
|
|
144
|
+
throw new Error('Graph manifest _comment must be a non-empty string');
|
|
145
|
+
}
|
|
146
|
+
assertPlainObject(packageJson, 'package.json');
|
|
147
|
+
if (typeof packageJson.version !== 'string' || !STRICT_VERSION.test(packageJson.version)) {
|
|
148
|
+
throw new Error(`package.json version is not strict MAJOR.MINOR.PATCH: ${packageJson.version}`);
|
|
149
|
+
}
|
|
150
|
+
assertPlainObject(manifest.assets, 'Graph manifest assets');
|
|
151
|
+
assertExactKeys(manifest.assets, Object.keys(GRAPH_PLATFORMS), 'Graph manifest assets');
|
|
152
|
+
|
|
153
|
+
for (const [platform, filename] of Object.entries(GRAPH_PLATFORMS)) {
|
|
154
|
+
const asset = manifest.assets[platform];
|
|
155
|
+
assertPlainObject(asset, `Graph asset ${platform}`);
|
|
156
|
+
assertExactKeys(asset, ['url', 'sha256'], `Graph asset ${platform}`);
|
|
157
|
+
if (typeof asset.url !== 'string') throw new Error(`${platform}: graph asset URL must be a string`);
|
|
158
|
+
let url;
|
|
159
|
+
try {
|
|
160
|
+
url = new URL(asset.url);
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error(`${platform}: invalid graph asset URL`);
|
|
163
|
+
}
|
|
164
|
+
const expectedPath = `/tribgames/mixdog/releases/download/graph-v${manifest.version}/${filename}`;
|
|
165
|
+
if (
|
|
166
|
+
url.protocol !== 'https:'
|
|
167
|
+
|| url.hostname !== 'github.com'
|
|
168
|
+
|| url.port
|
|
169
|
+
|| url.username
|
|
170
|
+
|| url.password
|
|
171
|
+
|| url.search
|
|
172
|
+
|| url.hash
|
|
173
|
+
|| url.pathname !== expectedPath
|
|
174
|
+
) {
|
|
175
|
+
throw new Error(`${platform}: graph asset URL must be https://github.com${expectedPath}`);
|
|
176
|
+
}
|
|
177
|
+
if (typeof asset.sha256 !== 'string' || !SHA256.test(asset.sha256)) {
|
|
178
|
+
throw new Error(`${platform}: invalid graph asset sha256`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return manifest;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function downloadAndVerify(label, asset, fetchImpl, timeoutMs, maxAssetBytes) {
|
|
185
|
+
console.log(`Downloading and verifying ${label}`);
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const response = await fetchImpl(asset.url, {
|
|
188
|
+
redirect: 'follow',
|
|
189
|
+
headers: { 'User-Agent': 'mixdog-release-asset-guard' },
|
|
190
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(timeoutMs)]),
|
|
191
|
+
});
|
|
192
|
+
if (!response.ok || !response.body) {
|
|
193
|
+
await response.body?.cancel();
|
|
194
|
+
throw new Error(`download failed with HTTP ${response.status}`);
|
|
195
|
+
}
|
|
196
|
+
const hash = createHash('sha256');
|
|
197
|
+
let downloaded = 0;
|
|
198
|
+
const byteCeiling = Math.min(asset.size ?? Number.POSITIVE_INFINITY, maxAssetBytes);
|
|
199
|
+
const reader = response.body.getReader();
|
|
200
|
+
try {
|
|
201
|
+
while (true) {
|
|
202
|
+
const { done, value: chunk } = await reader.read();
|
|
203
|
+
if (done) break;
|
|
204
|
+
if (downloaded + chunk.byteLength > byteCeiling) {
|
|
205
|
+
const error = new Error(`byte ceiling exceeded (${byteCeiling} bytes)`);
|
|
206
|
+
controller.abort(error);
|
|
207
|
+
await reader.cancel(error).catch(() => {});
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
hash.update(chunk);
|
|
211
|
+
downloaded += chunk.byteLength;
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
reader.releaseLock();
|
|
215
|
+
}
|
|
216
|
+
const digest = hash.digest('hex');
|
|
217
|
+
if (asset.size !== undefined && downloaded !== asset.size) {
|
|
218
|
+
throw new Error(`size mismatch (manifest ${asset.size}, downloaded ${downloaded})`);
|
|
219
|
+
}
|
|
220
|
+
if (digest.toLowerCase() !== asset.sha256.toLowerCase()) {
|
|
221
|
+
throw new Error(`sha256 mismatch (manifest ${asset.sha256}, downloaded ${digest})`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function verifyAssetDownloads(
|
|
226
|
+
assets,
|
|
227
|
+
{
|
|
228
|
+
fetchImpl = globalThis.fetch,
|
|
229
|
+
attempts = 3,
|
|
230
|
+
timeoutMs = 300_000,
|
|
231
|
+
maxAssetBytes = MAX_ASSET_BYTES,
|
|
232
|
+
retryDelay = (attempt) => (
|
|
233
|
+
new Promise((resolve) => setTimeout(resolve, attempt === 1 ? 2_000 : 6_000))
|
|
234
|
+
),
|
|
235
|
+
} = {},
|
|
236
|
+
) {
|
|
237
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 5) {
|
|
238
|
+
throw new Error(`Download attempts must be between 1 and 5, got ${attempts}`);
|
|
239
|
+
}
|
|
240
|
+
if (!Number.isSafeInteger(maxAssetBytes) || maxAssetBytes < 1 || maxAssetBytes > MAX_ASSET_BYTES) {
|
|
241
|
+
throw new Error(`Asset byte ceiling must be between 1 and ${MAX_ASSET_BYTES}, got ${maxAssetBytes}`);
|
|
242
|
+
}
|
|
243
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_DOWNLOAD_TIMEOUT_MS) {
|
|
244
|
+
throw new Error(`Download timeout must be between 1 and ${MAX_DOWNLOAD_TIMEOUT_MS}, got ${timeoutMs}`);
|
|
245
|
+
}
|
|
246
|
+
for (const [label, asset] of Object.entries(assets)) {
|
|
247
|
+
let lastError;
|
|
248
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
249
|
+
try {
|
|
250
|
+
await downloadAndVerify(label, asset, fetchImpl, timeoutMs, maxAssetBytes);
|
|
251
|
+
lastError = undefined;
|
|
252
|
+
break;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
lastError = error;
|
|
255
|
+
console.warn(`${label}: attempt ${attempt}/${attempts} failed: ${error.message}`);
|
|
256
|
+
if (attempt < attempts) await retryDelay(attempt);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (lastError) {
|
|
260
|
+
throw new Error(`${label}: verification failed after ${attempts} attempts: ${lastError.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function verifyReleaseAssets({
|
|
266
|
+
patchManifestPath = PATCH_MANIFEST_PATH,
|
|
267
|
+
cargoPath = PATCH_CARGO_PATH,
|
|
268
|
+
runtimeManifestPath = RUNTIME_MANIFEST_PATH,
|
|
269
|
+
graphManifestPath = GRAPH_MANIFEST_PATH,
|
|
270
|
+
packagePath = PACKAGE_PATH,
|
|
271
|
+
downloadOptions,
|
|
272
|
+
} = {}) {
|
|
273
|
+
const [patchSource, cargoToml, runtimeSource, graphSource, packageSource] = await Promise.all([
|
|
274
|
+
readFile(patchManifestPath, 'utf8'),
|
|
275
|
+
readFile(cargoPath, 'utf8'),
|
|
276
|
+
readFile(runtimeManifestPath, 'utf8'),
|
|
277
|
+
readFile(graphManifestPath, 'utf8'),
|
|
278
|
+
readFile(packagePath, 'utf8'),
|
|
279
|
+
]);
|
|
280
|
+
const patchManifest = validatePatchManifest(JSON.parse(patchSource), cargoToml);
|
|
281
|
+
const runtimeManifest = validateRuntimeManifest(JSON.parse(runtimeSource));
|
|
282
|
+
const graphManifest = validateGraphManifest(JSON.parse(graphSource), JSON.parse(packageSource));
|
|
283
|
+
await verifyAssetDownloads(patchManifest.assets, downloadOptions);
|
|
284
|
+
console.log(`Verified all bundled patch assets for patch-v${patchManifest.version}.`);
|
|
285
|
+
await verifyAssetDownloads(runtimeManifest.assets, downloadOptions);
|
|
286
|
+
console.log(`Verified all bundled runtime assets for ${runtimeManifest.release_tag}.`);
|
|
287
|
+
await verifyAssetDownloads(graphManifest.assets, downloadOptions);
|
|
288
|
+
console.log(`Verified all bundled graph assets for graph-v${graphManifest.version}.`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
292
|
+
await verifyReleaseAssets();
|
|
293
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
8
|
+
|
|
9
|
+
function source(relativePath) {
|
|
10
|
+
return readFileSync(join(root, relativePath), 'utf8');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('Windows-sensitive Node re-execs keep their windows hidden', () => {
|
|
14
|
+
const cli = source('src/cli.mjs');
|
|
15
|
+
const jitRebuild = source('src/tui/dev/jit-rebuild.mjs');
|
|
16
|
+
|
|
17
|
+
assert.match(cli, /spawnSync\(process\.execPath, \[fileURLToPath\(import\.meta\.url\), \.\.\.argv\], \{\r?\n\s*stdio: 'inherit',\r?\n\s*env: \{ \.\.\.process\.env, MIXDOG_SWAP_REEXEC: '1' \},\r?\n\s*windowsHide: true,\r?\n\s*\}\)/);
|
|
18
|
+
assert.match(jitRebuild, /spawnSync\(process\.execPath, \[script\], \{\r?\n\s*stdio: process\.env\.MIXDOG_TUI_DEV_VERBOSE \? 'inherit' : 'ignore',\r?\n\s*windowsHide: true,\r?\n\s*\}\)/);
|
|
19
|
+
});
|
package/src/cli.mjs
CHANGED
|
@@ -35,6 +35,7 @@ async function main() {
|
|
|
35
35
|
const r = spawnSync(process.execPath, [fileURLToPath(import.meta.url), ...argv], {
|
|
36
36
|
stdio: 'inherit',
|
|
37
37
|
env: { ...process.env, MIXDOG_SWAP_REEXEC: '1' },
|
|
38
|
+
windowsHide: true,
|
|
38
39
|
});
|
|
39
40
|
if (!r.error) return Number.isInteger(r.status) ? r.status : 0;
|
|
40
41
|
} catch { /* fall through to in-place run */ }
|
|
@@ -23,7 +23,9 @@ CORE is durable standing knowledge that lands in one of three layers:
|
|
|
23
23
|
- **L3 — Current map:** one-line project-landscape summaries, live long-running
|
|
24
24
|
goals, environment anchors documented nowhere else.
|
|
25
25
|
|
|
26
|
-
Every entry
|
|
26
|
+
Every entry must be ONE compact ENGLISH clause (≤120 chars), regardless of
|
|
27
|
+
source language. Keep identifiers, paths, and exact phrases coined by the user
|
|
28
|
+
verbatim. CORE is not a log.
|
|
27
29
|
|
|
28
30
|
## The distinction that decides every verdict
|
|
29
31
|
|
|
@@ -48,8 +50,10 @@ or inconclusive, keep the CORE entry.
|
|
|
48
50
|
|
|
49
51
|
## Verdicts
|
|
50
52
|
|
|
51
|
-
- `keep` — durable and already one
|
|
52
|
-
- `update` — durable but verbose
|
|
53
|
+
- `keep` — durable and already one compact ENGLISH clause.
|
|
54
|
+
- `update` — durable but verbose, multi-sentence, or non-English → rewrite as
|
|
55
|
+
one compact ≤120-char ENGLISH clause; keep identifiers, paths, and exact
|
|
56
|
+
phrases coined by the user verbatim.
|
|
53
57
|
- `merge` — duplicates another entry → fold into the survivor (same project pool).
|
|
54
58
|
- `reclassify` — the entry is filed under the WRONG project pool. Its subject
|
|
55
59
|
clearly belongs to a different pool shown among the entries under review: a
|
|
@@ -81,7 +85,9 @@ or inconclusive, keep the CORE entry.
|
|
|
81
85
|
`<id>|delete` with no reason, or any reason outside that set, is treated as
|
|
82
86
|
"needs confirmation" and only removed on an explicit APPLY CYCLE3 run.
|
|
83
87
|
|
|
84
|
-
A verbose durable entry is always `update`,
|
|
88
|
+
A verbose, multi-sentence, or non-English durable entry is always `update`,
|
|
89
|
+
never `keep`; rewrite it as a ≤120-char English clause while keeping
|
|
90
|
+
identifiers, paths, and exact user-coined phrases verbatim.
|
|
85
91
|
Delete is the rarest verdict. Prefer `keep` for durable rules/preferences and
|
|
86
92
|
`update` for compression when the current behavior is still valid.
|
|
87
93
|
Use `superseded` (not `delete`) when the entry WAS a valid durable rule/fact but
|
|
@@ -47,9 +47,10 @@ Use `why|A` for durable cross-session invariants (L1/L2). Use `why|B` for L3
|
|
|
47
47
|
current-map entries: one-line project-landscape summaries, live long-running
|
|
48
48
|
goals, and environment anchors documented nowhere else. `why` is validation
|
|
49
49
|
evidence only; it is
|
|
50
|
-
not stored. `core_summary` is injected into Core Memory, so
|
|
51
|
-
self-contained clause, <=120
|
|
52
|
-
|
|
50
|
+
not stored. `core_summary` is injected into Core Memory, so write it in ENGLISH
|
|
51
|
+
regardless of source language as one compact, self-contained clause, <=120
|
|
52
|
+
chars. Keep code identifiers, paths, and commands verbatim. For `merge`, `why`
|
|
53
|
+
and `core` may use the survivor `target_row`.
|
|
53
54
|
|
|
54
55
|
Allowed primary verbs:
|
|
55
56
|
|
|
@@ -245,15 +245,26 @@ export function parseGrepCoverage(resultText, toolName, toolArgs, resultKind) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
function classifyToolFailure(resultText, toolName) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
const raw = String(resultText ?? '');
|
|
249
|
+
const text = raw.toLowerCase();
|
|
250
|
+
// Shell renderers put the machine-readable status on the leading line.
|
|
251
|
+
// Only inspect that marker: command text and stderr frequently contain
|
|
252
|
+
// words such as "timeout" or "aborted" and must not rewrite a real exit
|
|
253
|
+
// code into a runtime failure category.
|
|
254
|
+
const leading = raw.split(/\r?\n/)
|
|
255
|
+
.map((line) => line.trim())
|
|
256
|
+
.find((line) => line && !line.startsWith('⚠️ '))
|
|
257
|
+
?.replace(/^Error:\s*/i, '') || '';
|
|
258
|
+
if (/^\[shell-tool-failed\](?:\s|$)/i.test(leading)) return 'tool-call/failure';
|
|
259
|
+
if (/^\[shell-run-failed\](?:\s|$)/i.test(leading)) {
|
|
260
|
+
if (/\[timeout:|cause:\s*(?:timeout|cancellation)\b/i.test(leading)) return 'timeout/abort';
|
|
261
|
+
if (/cause:\s*(?:output-limit|output-capture-error|background-adoption-failed)\b/i.test(leading)) return 'runtime/failure';
|
|
262
|
+
if (/\[signal:\s*[^\]]+/i.test(leading)) return 'process/signal';
|
|
252
263
|
return 'command-exit';
|
|
253
264
|
}
|
|
254
265
|
if (/requires either|invalid arguments|unknown parameter|must be|schema|expected|required|old_string is .*>=/.test(text)) return 'schema/args';
|
|
255
266
|
if (/not in allow-list|not allowed/.test(text)) return 'permission';
|
|
256
|
-
if (String(toolName || '') === 'shell' || /^\s*\[exit code:\s*\d+\]/i.test(
|
|
267
|
+
if (String(toolName || '') === 'shell' || /^\s*\[exit code:\s*\d+\]/i.test(raw)) return 'command-exit';
|
|
257
268
|
if (/enoent|cannot find|not found at this path|path does not exist|no such file|file not found in graph|unreadable/.test(text)) return 'path/enoent';
|
|
258
269
|
if (/timed out|timeout|interrupted|aborted/.test(text)) return 'timeout/abort';
|
|
259
270
|
if (/hunk rejected|patch failed|context mismatch|expected first old\/context|context not found/.test(text)) return 'patch/context';
|
|
@@ -246,7 +246,7 @@ function _sanitizeInputSchema(schema, toolName) {
|
|
|
246
246
|
// None of the branches' required lists are hoisted — callers that relied
|
|
247
247
|
// on discriminated-union semantics will still function; the model simply
|
|
248
248
|
// receives a union of the property surface with no hard-required constraint.
|
|
249
|
-
const mergedProps = {};
|
|
249
|
+
const mergedProps = { ...(schema.properties && typeof schema.properties === 'object' ? schema.properties : {}) };
|
|
250
250
|
const branchDescs = [];
|
|
251
251
|
for (const branch of Array.isArray(compound) ? compound : []) {
|
|
252
252
|
if (branch && typeof branch === 'object' && branch.properties) {
|
|
@@ -317,7 +317,26 @@ function nativeAnthropicTools(opts) {
|
|
|
317
317
|
function toAnthropicToolChoice(toolChoice) {
|
|
318
318
|
return toolChoice === 'none' ? { type: 'none' } : undefined;
|
|
319
319
|
}
|
|
320
|
-
function
|
|
320
|
+
function discoveredAnthropicToolNames(messages, opts, provider) {
|
|
321
|
+
const anthropicNative = new Set(['anthropic', 'anthropic-oauth']);
|
|
322
|
+
const discovered = new Set(
|
|
323
|
+
Array.isArray(opts?.session?.deferredDiscoveredTools)
|
|
324
|
+
? opts.session.deferredDiscoveredTools.map((name) => String(name || '').trim()).filter(Boolean)
|
|
325
|
+
: [],
|
|
326
|
+
);
|
|
327
|
+
for (const message of Array.isArray(messages) ? messages : []) {
|
|
328
|
+
const native = message?.nativeToolSearch;
|
|
329
|
+
const source = String(native?.provider || '').toLowerCase();
|
|
330
|
+
if (source && source !== provider
|
|
331
|
+
&& !(anthropicNative.has(source) && anthropicNative.has(provider))) continue;
|
|
332
|
+
for (const name of Array.isArray(native?.toolReferences) ? native.toolReferences : []) {
|
|
333
|
+
const key = String(name || '').trim();
|
|
334
|
+
if (key) discovered.add(key);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return discovered;
|
|
338
|
+
}
|
|
339
|
+
function deferredAnthropicTools(activeTools, messages, opts) {
|
|
321
340
|
if (opts?.session?.deferredNativeTools !== true) return [];
|
|
322
341
|
// A request whose ONLY tools are deferred is rejected by the API with
|
|
323
342
|
// `400: At least one tool must have defer_loading=false` — happens on the
|
|
@@ -325,9 +344,10 @@ function deferredAnthropicTools(activeTools, opts) {
|
|
|
325
344
|
// No active tools ⇒ send no deferred catalog either.
|
|
326
345
|
if (!Array.isArray(activeTools) || activeTools.length === 0) return [];
|
|
327
346
|
const active = new Set((activeTools || []).map((tool) => String(tool?.name || '').trim()).filter(Boolean));
|
|
347
|
+
const discovered = discoveredAnthropicToolNames(messages, opts, 'anthropic-oauth');
|
|
328
348
|
const catalog = Array.isArray(opts.session.deferredToolCatalog) ? opts.session.deferredToolCatalog : [];
|
|
329
349
|
return catalog
|
|
330
|
-
.filter((tool) => tool?.name && !active.has(String(tool.name)))
|
|
350
|
+
.filter((tool) => tool?.name && discovered.has(String(tool.name)) && !active.has(String(tool.name)))
|
|
331
351
|
.map((tool) => ({ ...tool, deferLoading: true }));
|
|
332
352
|
}
|
|
333
353
|
|
|
@@ -374,15 +394,19 @@ function toAnthropicMessages(messages) {
|
|
|
374
394
|
|
|
375
395
|
if (m.role === 'tool') {
|
|
376
396
|
const last = result[result.length - 1];
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
397
|
+
const native = m.nativeToolSearch;
|
|
398
|
+
const nativeProvider = String(native?.provider || '').toLowerCase();
|
|
399
|
+
const anthropicNative = new Set(['anthropic', 'anthropic-oauth']);
|
|
400
|
+
const references = (!nativeProvider || anthropicNative.has(nativeProvider))
|
|
401
|
+
&& Array.isArray(native?.toolReferences)
|
|
402
|
+
? native.toolReferences.map((name) => String(name || '').trim()).filter(Boolean)
|
|
403
|
+
: [];
|
|
382
404
|
const block = {
|
|
383
405
|
type: 'tool_result',
|
|
384
406
|
tool_use_id: m.toolCallId || '',
|
|
385
|
-
content:
|
|
407
|
+
content: references.length
|
|
408
|
+
? references.map((tool_name) => ({ type: 'tool_reference', tool_name }))
|
|
409
|
+
: normalizeContentForAnthropic(m.content),
|
|
386
410
|
};
|
|
387
411
|
if (last?.role === 'user' && Array.isArray(last.content)) {
|
|
388
412
|
last.content.push(block);
|
|
@@ -627,7 +651,7 @@ function buildRequestBody(messages, model, tools, sendOpts) {
|
|
|
627
651
|
if (systemBlocks.length) body.system = systemBlocks;
|
|
628
652
|
|
|
629
653
|
const nativeTools = nativeAnthropicTools(opts);
|
|
630
|
-
const deferredTools = deferredAnthropicTools(tools || [], opts);
|
|
654
|
+
const deferredTools = deferredAnthropicTools(tools || [], chatMsgs, opts);
|
|
631
655
|
if (tools?.length || nativeTools.length || deferredTools.length) {
|
|
632
656
|
// No cache_control on tools — the systemBase BP already covers the
|
|
633
657
|
// tools prefix via Anthropic's prompt cache prefix semantics (order:
|
|
@@ -1329,4 +1353,4 @@ export { parseSSEStream, _classifyMidstreamError, ANTHROPIC_MAX_MIDSTREAM_RETRIE
|
|
|
1329
1353
|
|
|
1330
1354
|
// Test-only escape hatch for scripts/tool-smoke.mjs to verify the
|
|
1331
1355
|
// catalog-driven max-tokens resolution without duplicating its logic.
|
|
1332
|
-
export const _test = { resolveMaxTokens };
|
|
1356
|
+
export const _test = { resolveMaxTokens, deferredAnthropicTools, sanitizeInputSchema: _sanitizeInputSchema };
|
|
@@ -231,7 +231,7 @@ function resolveMaxTokens(model) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
// Test-only escape hatch for scripts/anthropic-maxtokens-test.mjs.
|
|
234
|
-
export const _test = { resolveMaxTokens };
|
|
234
|
+
export const _test = { resolveMaxTokens, deferredAnthropicTools, sanitizeInputSchema: _sanitizeInputSchema };
|
|
235
235
|
|
|
236
236
|
const MIN_THINKING_BUDGET = 1024;
|
|
237
237
|
const THINKING_OUTPUT_RESERVE = 1024;
|
|
@@ -252,7 +252,7 @@ function _sanitizeInputSchema(schema, toolName) {
|
|
|
252
252
|
}
|
|
253
253
|
const compound = schema.oneOf || schema.anyOf || schema.allOf;
|
|
254
254
|
if (!compound) return structuredClone(schema);
|
|
255
|
-
const mergedProps = {};
|
|
255
|
+
const mergedProps = { ...(schema.properties && typeof schema.properties === 'object' ? schema.properties : {}) };
|
|
256
256
|
const branchDescs = [];
|
|
257
257
|
for (const branch of Array.isArray(compound) ? compound : []) {
|
|
258
258
|
if (branch && typeof branch === 'object' && branch.properties) {
|
|
@@ -324,16 +324,36 @@ function nativeAnthropicTools(opts) {
|
|
|
324
324
|
function toAnthropicToolChoice(toolChoice) {
|
|
325
325
|
return toolChoice === 'none' ? { type: 'none' } : undefined;
|
|
326
326
|
}
|
|
327
|
-
function
|
|
327
|
+
function discoveredAnthropicToolNames(messages, opts, provider) {
|
|
328
|
+
const anthropicNative = new Set(['anthropic', 'anthropic-oauth']);
|
|
329
|
+
const discovered = new Set(
|
|
330
|
+
Array.isArray(opts?.session?.deferredDiscoveredTools)
|
|
331
|
+
? opts.session.deferredDiscoveredTools.map((name) => String(name || '').trim()).filter(Boolean)
|
|
332
|
+
: [],
|
|
333
|
+
);
|
|
334
|
+
for (const message of Array.isArray(messages) ? messages : []) {
|
|
335
|
+
const native = message?.nativeToolSearch;
|
|
336
|
+
const source = String(native?.provider || '').toLowerCase();
|
|
337
|
+
if (source && source !== provider
|
|
338
|
+
&& !(anthropicNative.has(source) && anthropicNative.has(provider))) continue;
|
|
339
|
+
for (const name of Array.isArray(native?.toolReferences) ? native.toolReferences : []) {
|
|
340
|
+
const key = String(name || '').trim();
|
|
341
|
+
if (key) discovered.add(key);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return discovered;
|
|
345
|
+
}
|
|
346
|
+
function deferredAnthropicTools(activeTools, messages, opts) {
|
|
328
347
|
if (opts?.session?.deferredNativeTools !== true) return [];
|
|
329
348
|
// Guard against an all-deferred tools array — the API rejects it with
|
|
330
349
|
// `400: At least one tool must have defer_loading=false` (iteration-cap
|
|
331
350
|
// final turn sends tools: []). See anthropic-oauth.mjs counterpart.
|
|
332
351
|
if (!Array.isArray(activeTools) || activeTools.length === 0) return [];
|
|
333
352
|
const active = new Set((activeTools || []).map((tool) => String(tool?.name || '').trim()).filter(Boolean));
|
|
353
|
+
const discovered = discoveredAnthropicToolNames(messages, opts, 'anthropic');
|
|
334
354
|
const catalog = Array.isArray(opts.session.deferredToolCatalog) ? opts.session.deferredToolCatalog : [];
|
|
335
355
|
return catalog
|
|
336
|
-
.filter((tool) => tool?.name && !active.has(String(tool.name)))
|
|
356
|
+
.filter((tool) => tool?.name && discovered.has(String(tool.name)) && !active.has(String(tool.name)))
|
|
337
357
|
.map((tool) => ({ ...tool, deferLoading: true }));
|
|
338
358
|
}
|
|
339
359
|
function toAnthropicMessages(messages) {
|
|
@@ -376,15 +396,19 @@ function toAnthropicMessages(messages) {
|
|
|
376
396
|
}
|
|
377
397
|
if (m.role === 'tool') {
|
|
378
398
|
const last = result[result.length - 1];
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
399
|
+
const native = m.nativeToolSearch;
|
|
400
|
+
const nativeProvider = String(native?.provider || '').toLowerCase();
|
|
401
|
+
const anthropicNative = new Set(['anthropic', 'anthropic-oauth']);
|
|
402
|
+
const references = (!nativeProvider || anthropicNative.has(nativeProvider))
|
|
403
|
+
&& Array.isArray(native?.toolReferences)
|
|
404
|
+
? native.toolReferences.map((name) => String(name || '').trim()).filter(Boolean)
|
|
405
|
+
: [];
|
|
384
406
|
const block = {
|
|
385
407
|
type: 'tool_result',
|
|
386
408
|
tool_use_id: m.toolCallId || '',
|
|
387
|
-
content:
|
|
409
|
+
content: references.length
|
|
410
|
+
? references.map((tool_name) => ({ type: 'tool_reference', tool_name }))
|
|
411
|
+
: normalizeContentForAnthropic(m.content),
|
|
388
412
|
};
|
|
389
413
|
if (last?.role === 'user' && Array.isArray(last.content)) {
|
|
390
414
|
last.content.push(block);
|
|
@@ -620,7 +644,7 @@ export class AnthropicProvider {
|
|
|
620
644
|
if (tools?.length || nativeTools.length) {
|
|
621
645
|
// No cache_control on tools — the system BP covers tools via
|
|
622
646
|
// Anthropic prefix semantics (order: tools → system → messages).
|
|
623
|
-
params.tools = [...nativeTools, ...toAnthropicTools([...(tools || []), ...deferredAnthropicTools(tools || [], opts)])];
|
|
647
|
+
params.tools = [...nativeTools, ...toAnthropicTools([...(tools || []), ...deferredAnthropicTools(tools || [], chatMsgs, opts)])];
|
|
624
648
|
}
|
|
625
649
|
// tool_choice only when tools are actually present (Anthropic rejects
|
|
626
650
|
// tool_choice without tools). 'none' rides the hard-cap final turn to
|
|
@@ -47,3 +47,31 @@ export function customToolCallFromResponseItem(item) {
|
|
|
47
47
|
export function isCustomToolCallRecord(call) {
|
|
48
48
|
return call?.nativeType === 'custom_tool_call';
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
export function nativeToolSearchCallInput(call) {
|
|
52
|
+
if (call?.nativeType !== 'tool_search_call') return null;
|
|
53
|
+
return {
|
|
54
|
+
type: 'tool_search_call',
|
|
55
|
+
call_id: call.id || '',
|
|
56
|
+
execution: 'client',
|
|
57
|
+
arguments: call.arguments && typeof call.arguments === 'object' ? call.arguments : {},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function nativeToolSearchOutputInput(message, provider) {
|
|
62
|
+
const native = message?.nativeToolSearch;
|
|
63
|
+
const source = String(native?.provider || '').toLowerCase();
|
|
64
|
+
const target = String(provider || '').toLowerCase();
|
|
65
|
+
const openaiNative = new Set(['openai', 'openai-oauth']);
|
|
66
|
+
const sameNativeFamily = source === target
|
|
67
|
+
|| (openaiNative.has(source) && openaiNative.has(target));
|
|
68
|
+
if (!native || (source && !sameNativeFamily)) return null;
|
|
69
|
+
if (!Array.isArray(native.openaiTools)) return null;
|
|
70
|
+
return {
|
|
71
|
+
type: 'tool_search_output',
|
|
72
|
+
call_id: message.toolCallId || '',
|
|
73
|
+
status: 'completed',
|
|
74
|
+
execution: 'client',
|
|
75
|
+
tools: native.openaiTools,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -117,7 +117,7 @@ function functionToolFromSessionTool(t, name = t?.name) {
|
|
|
117
117
|
export function toResponsesTools(tools, options = {}) {
|
|
118
118
|
const provider = String(options?.provider || '').toLowerCase();
|
|
119
119
|
const allowNativeToolSearch = options?.nativeToolSearch === true
|
|
120
|
-
|| (options?.nativeToolSearch !== false && provider
|
|
120
|
+
|| (options?.nativeToolSearch !== false && (provider === 'openai' || provider === 'openai-oauth'));
|
|
121
121
|
return tools.map((t) => {
|
|
122
122
|
// load_tool advertises as the OpenAI-native `tool_search` wire type
|
|
123
123
|
// (legacy 'tool_search' name still accepted for back-compat). xAI/Grok
|