@tansr/sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +389 -121
- package/dist/index.js +1563 -460
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var TansrSdkError = class extends Error {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
// src/define-tool.ts
|
|
24
|
-
function specToZod(spec,
|
|
24
|
+
function specToZod(spec, path23) {
|
|
25
25
|
let schema;
|
|
26
26
|
switch (spec.type) {
|
|
27
27
|
case "string":
|
|
@@ -34,24 +34,24 @@ function specToZod(spec, path20) {
|
|
|
34
34
|
schema = z.boolean();
|
|
35
35
|
break;
|
|
36
36
|
case "array":
|
|
37
|
-
schema = z.array(spec.items !== void 0 ? specToZod(spec.items, `${
|
|
37
|
+
schema = z.array(spec.items !== void 0 ? specToZod(spec.items, `${path23}.items`) : z.unknown());
|
|
38
38
|
break;
|
|
39
39
|
case "object":
|
|
40
|
-
schema = spec.properties !== void 0 ? propertiesToZodObject(spec.properties,
|
|
40
|
+
schema = spec.properties !== void 0 ? propertiesToZodObject(spec.properties, path23) : z.record(z.unknown());
|
|
41
41
|
break;
|
|
42
42
|
default:
|
|
43
43
|
throw new TansrSdkError(
|
|
44
44
|
"invalid_options",
|
|
45
|
-
`defineTool: parameter "${
|
|
45
|
+
`defineTool: parameter "${path23}" has unknown type "${String(spec.type)}"; use one of 'string' | 'number' | 'boolean' | 'array' | 'object'.`
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
if (spec.description !== void 0) schema = schema.describe(spec.description);
|
|
49
49
|
return schema;
|
|
50
50
|
}
|
|
51
|
-
function propertiesToZodObject(properties,
|
|
51
|
+
function propertiesToZodObject(properties, path23) {
|
|
52
52
|
const shape = {};
|
|
53
53
|
for (const [key2, spec] of Object.entries(properties)) {
|
|
54
|
-
const field = specToZod(spec,
|
|
54
|
+
const field = specToZod(spec, path23 === "" ? key2 : `${path23}.${key2}`);
|
|
55
55
|
shape[key2] = spec.optional === true ? field.optional() : field;
|
|
56
56
|
}
|
|
57
57
|
return z.object(shape);
|
|
@@ -133,25 +133,25 @@ function defineTool(options) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// src/skills.ts
|
|
136
|
-
import
|
|
136
|
+
import path21 from "node:path";
|
|
137
137
|
|
|
138
138
|
// ../kernel/src/world/default-fs.ts
|
|
139
139
|
import fs from "node:fs/promises";
|
|
140
140
|
var defaultFs = {
|
|
141
|
-
stat(
|
|
142
|
-
return fs.stat(
|
|
141
|
+
stat(path23) {
|
|
142
|
+
return fs.stat(path23);
|
|
143
143
|
},
|
|
144
|
-
lstat(
|
|
145
|
-
return fs.lstat(
|
|
144
|
+
lstat(path23) {
|
|
145
|
+
return fs.lstat(path23);
|
|
146
146
|
},
|
|
147
|
-
realpath(
|
|
148
|
-
return fs.realpath(
|
|
147
|
+
realpath(path23) {
|
|
148
|
+
return fs.realpath(path23);
|
|
149
149
|
},
|
|
150
|
-
readFile(
|
|
151
|
-
return fs.readFile(
|
|
150
|
+
readFile(path23) {
|
|
151
|
+
return fs.readFile(path23);
|
|
152
152
|
},
|
|
153
|
-
async readFilePrefix(
|
|
154
|
-
const handle = await fs.open(
|
|
153
|
+
async readFilePrefix(path23, bytes) {
|
|
154
|
+
const handle = await fs.open(path23, "r");
|
|
155
155
|
try {
|
|
156
156
|
const prefix = Buffer.alloc(bytes);
|
|
157
157
|
const { bytesRead } = await handle.read(prefix, 0, bytes, 0);
|
|
@@ -160,17 +160,17 @@ var defaultFs = {
|
|
|
160
160
|
await handle.close();
|
|
161
161
|
}
|
|
162
162
|
},
|
|
163
|
-
async writeFile(
|
|
164
|
-
await fs.writeFile(
|
|
163
|
+
async writeFile(path23, data) {
|
|
164
|
+
await fs.writeFile(path23, data);
|
|
165
165
|
},
|
|
166
|
-
async appendFile(
|
|
167
|
-
await fs.appendFile(
|
|
166
|
+
async appendFile(path23, data) {
|
|
167
|
+
await fs.appendFile(path23, data);
|
|
168
168
|
},
|
|
169
|
-
async mkdir(
|
|
170
|
-
await fs.mkdir(
|
|
169
|
+
async mkdir(path23, opts) {
|
|
170
|
+
await fs.mkdir(path23, opts);
|
|
171
171
|
},
|
|
172
|
-
readdir(
|
|
173
|
-
return fs.readdir(
|
|
172
|
+
readdir(path23) {
|
|
173
|
+
return fs.readdir(path23, { withFileTypes: true });
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
|
|
@@ -1141,6 +1141,14 @@ var FileCheckpointSchema = z4.object({
|
|
|
1141
1141
|
/** true = 文件存在但超采集上限,内容未存(回滚时如实跳过) */
|
|
1142
1142
|
skipped: z4.boolean().optional()
|
|
1143
1143
|
});
|
|
1144
|
+
var Visibilities = {
|
|
1145
|
+
/** 用户与助手正常消息:全可见 */
|
|
1146
|
+
normal: { model: true, defaultTranscript: true, expandedTranscript: true, sdk: true },
|
|
1147
|
+
/** 元信息(如 system-reminder):进模型但默认转录隐藏 */
|
|
1148
|
+
meta: { model: true, defaultTranscript: false, expandedTranscript: true, sdk: false },
|
|
1149
|
+
/** 内部记录:不进模型、仅展开转录可见 */
|
|
1150
|
+
internal: { model: false, defaultTranscript: false, expandedTranscript: true, sdk: false }
|
|
1151
|
+
};
|
|
1144
1152
|
|
|
1145
1153
|
// ../protocol/src/config.ts
|
|
1146
1154
|
import { z as z5 } from "zod";
|
|
@@ -1447,8 +1455,9 @@ var PermissionsSectionSchema = z5.object({
|
|
|
1447
1455
|
* TANSR_CLASSIFIER_TIGHTEN_OUTSIDE_AUTO / TANSR_CLASSIFIER_BUDGET 保留为
|
|
1448
1456
|
* 最高优先覆盖(逐键接管)。仅 user/managed 层被尊重(T-K0:分类器属
|
|
1449
1457
|
* 用户/企业的信任与成本决策,项目层声明整段忽略+示警)。
|
|
1450
|
-
*
|
|
1451
|
-
* break-glass
|
|
1458
|
+
* 史注:TANSR_CLASSIFIER_ALLOW_UNQUALIFIED(资格越过)曾刻意不进配置
|
|
1459
|
+
* (break-glass 性质恒 env-only),已随 UAA P4-C2 整体退役(2026-08-30,
|
|
1460
|
+
* 任命即授照后无「未达资格」可越)。
|
|
1452
1461
|
*/
|
|
1453
1462
|
classifier: z5.object({
|
|
1454
1463
|
enabled: z5.boolean().optional(),
|
|
@@ -7559,7 +7568,7 @@ async function* consumeModelStream(deps) {
|
|
|
7559
7568
|
const stream = client.stream(request, callOptions);
|
|
7560
7569
|
const collected = /* @__PURE__ */ new Map();
|
|
7561
7570
|
const order = [];
|
|
7562
|
-
const
|
|
7571
|
+
const open4 = /* @__PURE__ */ new Set();
|
|
7563
7572
|
const assembler = new ToolArgsAssembler();
|
|
7564
7573
|
const toolCalls = [];
|
|
7565
7574
|
let stopEvent;
|
|
@@ -7608,7 +7617,7 @@ async function* consumeModelStream(deps) {
|
|
|
7608
7617
|
text: ev.block.t === "text" || ev.block.t === "thinking" ? ev.block.text : ""
|
|
7609
7618
|
});
|
|
7610
7619
|
order.push(ev.index);
|
|
7611
|
-
|
|
7620
|
+
open4.add(ev.index);
|
|
7612
7621
|
yield emit({ type: "msg.block.start", index: ev.index, blockType: ev.block.t });
|
|
7613
7622
|
break;
|
|
7614
7623
|
}
|
|
@@ -7633,7 +7642,7 @@ async function* consumeModelStream(deps) {
|
|
|
7633
7642
|
assembler.append(ev.index, ev.json);
|
|
7634
7643
|
break;
|
|
7635
7644
|
case "block_stop": {
|
|
7636
|
-
|
|
7645
|
+
open4.delete(ev.index);
|
|
7637
7646
|
yield emit({ type: "msg.block.end", index: ev.index });
|
|
7638
7647
|
const c = collected.get(ev.index);
|
|
7639
7648
|
if (c && c.base.t === "tool_call") {
|
|
@@ -7674,16 +7683,16 @@ async function* consumeModelStream(deps) {
|
|
|
7674
7683
|
}
|
|
7675
7684
|
}
|
|
7676
7685
|
}
|
|
7677
|
-
const completedTextBlocks = () => order.filter((index) => !
|
|
7686
|
+
const completedTextBlocks = () => order.filter((index) => !open4.has(index)).map((index) => collected.get(index)).filter(
|
|
7678
7687
|
(c) => c !== void 0 && c.base.t === "text" && c.text.trim().length > 0
|
|
7679
7688
|
).map((c) => finalizeBlock(c));
|
|
7680
7689
|
if (abortedMidStream || signal.aborted) {
|
|
7681
|
-
for (const index of
|
|
7690
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7682
7691
|
disposeGenerator(stream);
|
|
7683
7692
|
return { kind: "aborted" };
|
|
7684
7693
|
}
|
|
7685
7694
|
if (errorEvent) {
|
|
7686
|
-
for (const index of
|
|
7695
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7687
7696
|
disposeGenerator(stream);
|
|
7688
7697
|
return {
|
|
7689
7698
|
kind: "error",
|
|
@@ -7697,7 +7706,7 @@ async function* consumeModelStream(deps) {
|
|
|
7697
7706
|
};
|
|
7698
7707
|
}
|
|
7699
7708
|
if (!stopEvent) {
|
|
7700
|
-
for (const index of
|
|
7709
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7701
7710
|
return {
|
|
7702
7711
|
kind: "error",
|
|
7703
7712
|
error: {
|
|
@@ -7709,7 +7718,7 @@ async function* consumeModelStream(deps) {
|
|
|
7709
7718
|
};
|
|
7710
7719
|
}
|
|
7711
7720
|
if (stopEvent.stopReason === "unknown") {
|
|
7712
|
-
for (const index of
|
|
7721
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7713
7722
|
disposeGenerator(stream);
|
|
7714
7723
|
return {
|
|
7715
7724
|
kind: "error",
|
|
@@ -9839,16 +9848,16 @@ function unescapeSpecifier(spec) {
|
|
|
9839
9848
|
function parseRule(raw) {
|
|
9840
9849
|
const trimmed = raw.trim();
|
|
9841
9850
|
if (trimmed === "") return null;
|
|
9842
|
-
const
|
|
9843
|
-
if (
|
|
9851
|
+
const open4 = findUnescapedParen(trimmed);
|
|
9852
|
+
if (open4 === -1) {
|
|
9844
9853
|
if (TOOL_NAME_RE.test(trimmed)) return { raw: trimmed, toolName: trimmed };
|
|
9845
9854
|
if (isMcpServerWildcard(trimmed)) return { raw: trimmed, toolName: trimmed };
|
|
9846
9855
|
return null;
|
|
9847
9856
|
}
|
|
9848
9857
|
if (!trimmed.endsWith(")")) return null;
|
|
9849
|
-
const toolName2 = trimmed.slice(0,
|
|
9858
|
+
const toolName2 = trimmed.slice(0, open4);
|
|
9850
9859
|
if (!TOOL_NAME_RE.test(toolName2)) return null;
|
|
9851
|
-
const spec = unescapeSpecifier(trimmed.slice(
|
|
9860
|
+
const spec = unescapeSpecifier(trimmed.slice(open4 + 1, -1)).trim();
|
|
9852
9861
|
if (spec === "" || spec === "*") return { raw: trimmed, toolName: toolName2 };
|
|
9853
9862
|
return { raw: trimmed, toolName: toolName2, specifier: spec };
|
|
9854
9863
|
}
|
|
@@ -10092,9 +10101,9 @@ function stripBom(text) {
|
|
|
10092
10101
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
10093
10102
|
}
|
|
10094
10103
|
function stripFrontmatter(text) {
|
|
10095
|
-
const
|
|
10096
|
-
if (
|
|
10097
|
-
const rest = text.slice(
|
|
10104
|
+
const open4 = /^---[ \t]*\r?\n/.exec(text);
|
|
10105
|
+
if (open4 === null) return text;
|
|
10106
|
+
const rest = text.slice(open4[0].length);
|
|
10098
10107
|
const close = /^---[ \t]*(?:\r?\n|$)/m.exec(rest);
|
|
10099
10108
|
if (close === null) return text;
|
|
10100
10109
|
return rest.slice(close.index + close[0].length);
|
|
@@ -10161,9 +10170,9 @@ var TYPE_KEY_RE = /^type[ \t]*:[ \t]*(.*)$/;
|
|
|
10161
10170
|
function memoryEntryTypeOf(content, indexFile) {
|
|
10162
10171
|
if (indexFile) return "unknown";
|
|
10163
10172
|
const text = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
10164
|
-
const
|
|
10165
|
-
if (
|
|
10166
|
-
const rest = text.slice(
|
|
10173
|
+
const open4 = FRONTMATTER_OPEN_RE.exec(text);
|
|
10174
|
+
if (open4 === null) return "unknown";
|
|
10175
|
+
const rest = text.slice(open4[0].length);
|
|
10167
10176
|
const close = FRONTMATTER_CLOSE_RE.exec(rest);
|
|
10168
10177
|
if (close === null) return "unknown";
|
|
10169
10178
|
for (const rawLine of rest.slice(0, close.index).split("\n")) {
|
|
@@ -10651,6 +10660,7 @@ var CLASSIFIER_BREAKER_TOTAL_LIMIT = 20;
|
|
|
10651
10660
|
var CLASSIFIER_BREAKER_COOLDOWN_MS = 30 * 6e4;
|
|
10652
10661
|
|
|
10653
10662
|
// ../kernel/src/journal/hash.ts
|
|
10663
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
10654
10664
|
function sortKeysDeep2(value) {
|
|
10655
10665
|
if (Array.isArray(value)) return value.map(sortKeysDeep2);
|
|
10656
10666
|
if (value !== null && typeof value === "object") {
|
|
@@ -10667,6 +10677,11 @@ function canonicalStringify(value) {
|
|
|
10667
10677
|
const normalized = JSON.parse(JSON.stringify(value));
|
|
10668
10678
|
return JSON.stringify(sortKeysDeep2(normalized));
|
|
10669
10679
|
}
|
|
10680
|
+
function computeRecordHash(record) {
|
|
10681
|
+
const { integrity, ...rest } = record;
|
|
10682
|
+
const hashable = { ...rest, integrity: { previousHash: integrity.previousHash } };
|
|
10683
|
+
return createHash5("sha256").update(canonicalStringify(hashable), "utf8").digest("hex");
|
|
10684
|
+
}
|
|
10670
10685
|
|
|
10671
10686
|
// ../kernel/src/permissions/broad-allow.ts
|
|
10672
10687
|
var BROAD_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
@@ -11146,13 +11161,13 @@ function canonicalCwd(cwd) {
|
|
|
11146
11161
|
const resolved = resolveForPermission(cwd, ".");
|
|
11147
11162
|
return resolved.kind === "ok" ? resolved.path : null;
|
|
11148
11163
|
}
|
|
11149
|
-
function canonicalPathEligible(cwdCanon,
|
|
11150
|
-
if (hasWindowsDeviceSegment(
|
|
11151
|
-
if (
|
|
11152
|
-
if (!
|
|
11153
|
-
if (guard.isProtectedContentPath(
|
|
11154
|
-
if (matchProtectedPath(
|
|
11155
|
-
const segments =
|
|
11164
|
+
function canonicalPathEligible(cwdCanon, path23, policy, guard) {
|
|
11165
|
+
if (hasWindowsDeviceSegment(path23)) return false;
|
|
11166
|
+
if (path23 === cwdCanon) return !policy.strict;
|
|
11167
|
+
if (!path23.startsWith(`${cwdCanon}/`)) return false;
|
|
11168
|
+
if (guard.isProtectedContentPath(path23)) return false;
|
|
11169
|
+
if (matchProtectedPath(path23, null) !== null) return false;
|
|
11170
|
+
const segments = path23.slice(cwdCanon.length + 1).split("/");
|
|
11156
11171
|
for (const [index, seg] of segments.entries()) {
|
|
11157
11172
|
const hasGlob = GLOB_CHAR_RE.test(seg);
|
|
11158
11173
|
if (hasGlob && seg.startsWith(".")) return false;
|
|
@@ -11336,9 +11351,9 @@ function verifyBundle(bundle, opts = {}) {
|
|
|
11336
11351
|
// ../kernel/src/permissions/soften-radius.ts
|
|
11337
11352
|
var PROTECTED_PATH_RULE_PREFIX = "protected-path(";
|
|
11338
11353
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
11339
|
-
function pathInsideRoot(
|
|
11354
|
+
function pathInsideRoot(path23, root) {
|
|
11340
11355
|
if (root === null) return false;
|
|
11341
|
-
return
|
|
11356
|
+
return path23 === root || path23.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
11342
11357
|
}
|
|
11343
11358
|
function classifySoftenFace(input, base) {
|
|
11344
11359
|
if (base.matchedRule?.startsWith(PROTECTED_PATH_RULE_PREFIX) === true) return "F6";
|
|
@@ -11970,14 +11985,14 @@ var PermissionEngine = class {
|
|
|
11970
11985
|
/**
|
|
11971
11986
|
* 生效资格档解析(T-K23c,16 §4.5):解析闭包在场即**现算**(装配层合成
|
|
11972
11987
|
* min(entry, ceiling, override);闭包抛错 → 'none',词表外脏值由
|
|
11973
|
-
* softenRadiusAllows 安全查表兜底——T-INV-B 一切异常朝严)
|
|
11974
|
-
*
|
|
11975
|
-
*
|
|
11988
|
+
* softenRadiusAllows 安全查表兜底——T-INV-B 一切异常朝严);闭包缺席
|
|
11989
|
+
* 恒 'none'(无任命即无软化面;@deprecated 布尔位 qualifiedForAllow
|
|
11990
|
+
* 已随 P4-C3 拆除,2026-08-30)。
|
|
11976
11991
|
*/
|
|
11977
11992
|
resolveClassifierTier(config) {
|
|
11978
11993
|
const resolve2 = config.resolveQualificationTier;
|
|
11979
11994
|
if (resolve2 === void 0) {
|
|
11980
|
-
return
|
|
11995
|
+
return "none";
|
|
11981
11996
|
}
|
|
11982
11997
|
try {
|
|
11983
11998
|
return resolve2();
|
|
@@ -12642,8 +12657,8 @@ function fsErrorCode(err) {
|
|
|
12642
12657
|
}
|
|
12643
12658
|
|
|
12644
12659
|
// ../kernel/src/tools/files/real-target-guard.ts
|
|
12645
|
-
function accessPhrases(
|
|
12646
|
-
return
|
|
12660
|
+
function accessPhrases(access2) {
|
|
12661
|
+
return access2 === "read" ? { doing: "reading from", refused: "Reading is refused" } : { doing: "writing to", refused: "Writing is refused" };
|
|
12647
12662
|
}
|
|
12648
12663
|
async function findExistingAnchor(fs3, abs) {
|
|
12649
12664
|
let cur = abs;
|
|
@@ -12670,8 +12685,8 @@ function insideCanonical(child, base, caseInsensitive) {
|
|
|
12670
12685
|
const b = caseInsensitive ? base.toLowerCase() : base;
|
|
12671
12686
|
return c === b || c.startsWith(b.endsWith("/") ? b : `${b}/`);
|
|
12672
12687
|
}
|
|
12673
|
-
function refusal(toolName2, declared, realTarget, code, detail,
|
|
12674
|
-
const phrase = accessPhrases(
|
|
12688
|
+
function refusal(toolName2, declared, realTarget, code, detail, access2) {
|
|
12689
|
+
const phrase = accessPhrases(access2);
|
|
12675
12690
|
return {
|
|
12676
12691
|
ok: false,
|
|
12677
12692
|
code,
|
|
@@ -12681,8 +12696,8 @@ function refusal(toolName2, declared, realTarget, code, detail, access) {
|
|
|
12681
12696
|
}
|
|
12682
12697
|
async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
12683
12698
|
const caseInsensitive = options.caseInsensitivePaths ?? process.platform === "win32";
|
|
12684
|
-
const
|
|
12685
|
-
const phrase = accessPhrases(
|
|
12699
|
+
const access2 = options.access ?? "write";
|
|
12700
|
+
const phrase = accessPhrases(access2);
|
|
12686
12701
|
const fs3 = options.fs ?? defaultFs;
|
|
12687
12702
|
let anchor;
|
|
12688
12703
|
try {
|
|
@@ -12720,7 +12735,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12720
12735
|
realTarget,
|
|
12721
12736
|
"unverifiable",
|
|
12722
12737
|
"The redirected target cannot be safely canonicalized for comparison.",
|
|
12723
|
-
|
|
12738
|
+
access2
|
|
12724
12739
|
);
|
|
12725
12740
|
}
|
|
12726
12741
|
const memoryDirs = options.memoryCarveOutDirs ?? [];
|
|
@@ -12766,7 +12781,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12766
12781
|
realTarget,
|
|
12767
12782
|
"memory_divergence",
|
|
12768
12783
|
"Memory files must be plain files: no path component of a memory target may be a symlink or junction.",
|
|
12769
|
-
|
|
12784
|
+
access2
|
|
12770
12785
|
);
|
|
12771
12786
|
}
|
|
12772
12787
|
} else if (realInMemory) {
|
|
@@ -12776,7 +12791,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12776
12791
|
realTarget,
|
|
12777
12792
|
"memory_divergence",
|
|
12778
12793
|
"The real target is inside the agent memory directory, which may only be addressed directly.",
|
|
12779
|
-
|
|
12794
|
+
access2
|
|
12780
12795
|
);
|
|
12781
12796
|
}
|
|
12782
12797
|
}
|
|
@@ -12798,7 +12813,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12798
12813
|
realTarget,
|
|
12799
12814
|
"escapes_workspace",
|
|
12800
12815
|
`The real target is outside the workspace root "${options.cwd}".`,
|
|
12801
|
-
|
|
12816
|
+
access2
|
|
12802
12817
|
);
|
|
12803
12818
|
}
|
|
12804
12819
|
const homeC = canonicalHomedir(options.homedir ?? os.homedir());
|
|
@@ -12811,7 +12826,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12811
12826
|
realTarget,
|
|
12812
12827
|
"protected_divergence",
|
|
12813
12828
|
`The real target is inside a protected area (${realLabel}).`,
|
|
12814
|
-
|
|
12829
|
+
access2
|
|
12815
12830
|
);
|
|
12816
12831
|
}
|
|
12817
12832
|
if (realC.kind === "ok" && matchesSecretFace(realC.path) && !(declaredC.kind === "ok" && matchesSecretFace(declaredC.path))) {
|
|
@@ -12821,7 +12836,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12821
12836
|
realTarget,
|
|
12822
12837
|
"secret_divergence",
|
|
12823
12838
|
"The real target matches a protected secret-file pattern.",
|
|
12824
|
-
|
|
12839
|
+
access2
|
|
12825
12840
|
);
|
|
12826
12841
|
}
|
|
12827
12842
|
return { ok: true, realPath: realTarget, linkResolved: true };
|
|
@@ -12950,6 +12965,47 @@ function buildDecisionRecord(params) {
|
|
|
12950
12965
|
}
|
|
12951
12966
|
|
|
12952
12967
|
// ../kernel/src/tools/dispatch/dispatching-tool-executor.ts
|
|
12968
|
+
function suggestToolName(attempted, registered) {
|
|
12969
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
12970
|
+
const a = norm(attempted);
|
|
12971
|
+
if (a === "") return null;
|
|
12972
|
+
let prefixHit = null;
|
|
12973
|
+
let levHit = null;
|
|
12974
|
+
let levBest = 3;
|
|
12975
|
+
for (const name of registered) {
|
|
12976
|
+
const n = norm(name);
|
|
12977
|
+
if (n === a) return name;
|
|
12978
|
+
if (prefixHit === null && Math.min(n.length, a.length) >= 4 && (n.startsWith(a) || a.startsWith(n))) {
|
|
12979
|
+
prefixHit = name;
|
|
12980
|
+
}
|
|
12981
|
+
const d = boundedLevenshtein(a, n, 2);
|
|
12982
|
+
if (d !== null && d < levBest) {
|
|
12983
|
+
levBest = d;
|
|
12984
|
+
levHit = name;
|
|
12985
|
+
}
|
|
12986
|
+
}
|
|
12987
|
+
return prefixHit ?? levHit;
|
|
12988
|
+
}
|
|
12989
|
+
function boundedLevenshtein(a, b, cap) {
|
|
12990
|
+
if (Math.abs(a.length - b.length) > cap) return null;
|
|
12991
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
12992
|
+
for (let i = 1; i <= a.length; i++) {
|
|
12993
|
+
const cur = [i, ...Array.from({ length: b.length }, () => 0)];
|
|
12994
|
+
let rowMin = i;
|
|
12995
|
+
for (let j = 1; j <= b.length; j++) {
|
|
12996
|
+
cur[j] = Math.min(
|
|
12997
|
+
prev[j] + 1,
|
|
12998
|
+
cur[j - 1] + 1,
|
|
12999
|
+
prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
13000
|
+
);
|
|
13001
|
+
rowMin = Math.min(rowMin, cur[j]);
|
|
13002
|
+
}
|
|
13003
|
+
if (rowMin > cap) return null;
|
|
13004
|
+
prev = cur;
|
|
13005
|
+
}
|
|
13006
|
+
const d = prev[b.length];
|
|
13007
|
+
return d <= cap ? d : null;
|
|
13008
|
+
}
|
|
12953
13009
|
var DEFAULT_MAX_CONCURRENCY = 8;
|
|
12954
13010
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
12955
13011
|
var unsafeNoGateConstruction = false;
|
|
@@ -13149,7 +13205,8 @@ var DispatchingToolExecutor = class {
|
|
|
13149
13205
|
const tool = this.#registry.get(call.name);
|
|
13150
13206
|
if (tool === void 0) {
|
|
13151
13207
|
const registered = this.#registry.names();
|
|
13152
|
-
const
|
|
13208
|
+
const suggested = suggestToolName(call.name, registered);
|
|
13209
|
+
const message = `Unknown tool "${call.name}". ` + (suggested !== null ? `Did you mean "${suggested}"? ` : "") + (registered.length > 0 ? `Available tools: ${registered.join(", ")}.` : "No tools are registered.");
|
|
13153
13210
|
yield {
|
|
13154
13211
|
t: "event",
|
|
13155
13212
|
body: { type: "tool.failed", toolCallId: call.id, errorType: "unknown_tool", message }
|
|
@@ -13714,7 +13771,31 @@ var DispatchingToolExecutor = class {
|
|
|
13714
13771
|
}
|
|
13715
13772
|
};
|
|
13716
13773
|
|
|
13774
|
+
// ../kernel/src/journal/errors.ts
|
|
13775
|
+
var JournalError = class extends Error {
|
|
13776
|
+
code;
|
|
13777
|
+
constructor(code, message) {
|
|
13778
|
+
super(message);
|
|
13779
|
+
this.name = "JournalError";
|
|
13780
|
+
this.code = code;
|
|
13781
|
+
}
|
|
13782
|
+
};
|
|
13783
|
+
function isErrnoException(err) {
|
|
13784
|
+
return err instanceof Error && typeof err.code === "string";
|
|
13785
|
+
}
|
|
13786
|
+
var JournalCorruptionError = class extends JournalError {
|
|
13787
|
+
/** 损坏所在行号(1-based) */
|
|
13788
|
+
line;
|
|
13789
|
+
constructor(line, detail) {
|
|
13790
|
+
super("JOURNAL_CORRUPTED", `journal 第 ${line} 行数据损坏:${detail}`);
|
|
13791
|
+
this.name = "JournalCorruptionError";
|
|
13792
|
+
this.line = line;
|
|
13793
|
+
}
|
|
13794
|
+
};
|
|
13795
|
+
|
|
13717
13796
|
// ../kernel/src/journal/paths.ts
|
|
13797
|
+
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
|
|
13798
|
+
import path5 from "node:path";
|
|
13718
13799
|
import { z as z14 } from "zod";
|
|
13719
13800
|
var SessionMetaSchema = z14.object({
|
|
13720
13801
|
schemaVersion: z14.literal(1),
|
|
@@ -13744,12 +13825,543 @@ var SessionMetaSchema = z14.object({
|
|
|
13744
13825
|
*/
|
|
13745
13826
|
outputStyle: z14.string().optional()
|
|
13746
13827
|
});
|
|
13828
|
+
var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13829
|
+
function assertValidSessionId(sessionId) {
|
|
13830
|
+
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
13831
|
+
throw new JournalError(
|
|
13832
|
+
"INVALID_SESSION_ID",
|
|
13833
|
+
`非法 sessionId ${JSON.stringify(sessionId)}:仅允许字母数字与 . _ -,且须以字母数字开头`
|
|
13834
|
+
);
|
|
13835
|
+
}
|
|
13836
|
+
}
|
|
13837
|
+
function sessionDirPath(storageRoot, sessionId) {
|
|
13838
|
+
assertValidSessionId(sessionId);
|
|
13839
|
+
return path5.join(storageRoot, "sessions", sessionId);
|
|
13840
|
+
}
|
|
13841
|
+
function journalFilePath(sessionDir) {
|
|
13842
|
+
return path5.join(sessionDir, "journal.jsonl");
|
|
13843
|
+
}
|
|
13844
|
+
function metaFilePath(sessionDir) {
|
|
13845
|
+
return path5.join(sessionDir, "meta.json");
|
|
13846
|
+
}
|
|
13847
|
+
function lockFilePath(sessionDir) {
|
|
13848
|
+
return path5.join(sessionDir, "lock");
|
|
13849
|
+
}
|
|
13850
|
+
var durableTmpSeq = 0;
|
|
13851
|
+
async function writeFileDurable(filePath, content) {
|
|
13852
|
+
durableTmpSeq += 1;
|
|
13853
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${durableTmpSeq.toString(36)}-${Date.now()}`;
|
|
13854
|
+
const handle = await open(tempPath, "w");
|
|
13855
|
+
try {
|
|
13856
|
+
if (typeof content === "string") {
|
|
13857
|
+
await handle.writeFile(content, "utf8");
|
|
13858
|
+
} else {
|
|
13859
|
+
await handle.writeFile(content);
|
|
13860
|
+
}
|
|
13861
|
+
await handle.sync();
|
|
13862
|
+
} finally {
|
|
13863
|
+
await handle.close();
|
|
13864
|
+
}
|
|
13865
|
+
try {
|
|
13866
|
+
await rename(tempPath, filePath);
|
|
13867
|
+
} catch (err) {
|
|
13868
|
+
await rm(tempPath, { force: true }).catch(() => void 0);
|
|
13869
|
+
throw err;
|
|
13870
|
+
}
|
|
13871
|
+
}
|
|
13872
|
+
async function readSessionMeta(sessionDir) {
|
|
13873
|
+
const raw = await readFile(metaFilePath(sessionDir), "utf8");
|
|
13874
|
+
let parsed;
|
|
13875
|
+
try {
|
|
13876
|
+
parsed = JSON.parse(raw);
|
|
13877
|
+
} catch {
|
|
13878
|
+
throw new JournalError("META_INVALID", `meta.json 不是合法 JSON:${metaFilePath(sessionDir)}`);
|
|
13879
|
+
}
|
|
13880
|
+
const result = SessionMetaSchema.safeParse(parsed);
|
|
13881
|
+
if (!result.success) {
|
|
13882
|
+
throw new JournalError("META_INVALID", `meta.json 不符合 SessionMeta schema:${result.error.message}`);
|
|
13883
|
+
}
|
|
13884
|
+
return result.data;
|
|
13885
|
+
}
|
|
13886
|
+
async function ensureSessionDir(options) {
|
|
13887
|
+
const sessionDir = sessionDirPath(options.storageRoot, options.sessionId);
|
|
13888
|
+
await mkdir(sessionDir, { recursive: true });
|
|
13889
|
+
try {
|
|
13890
|
+
const meta2 = await readSessionMeta(sessionDir);
|
|
13891
|
+
if (meta2.sessionId !== options.sessionId) {
|
|
13892
|
+
throw new JournalError(
|
|
13893
|
+
"META_MISMATCH",
|
|
13894
|
+
`meta.json 中 sessionId(${meta2.sessionId})与目录名(${options.sessionId})不一致`
|
|
13895
|
+
);
|
|
13896
|
+
}
|
|
13897
|
+
return { sessionDir, meta: meta2, created: false };
|
|
13898
|
+
} catch (err) {
|
|
13899
|
+
if (!isErrnoException(err) || err.code !== "ENOENT") throw err;
|
|
13900
|
+
}
|
|
13901
|
+
const meta = {
|
|
13902
|
+
schemaVersion: 1,
|
|
13903
|
+
sessionId: options.sessionId,
|
|
13904
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13905
|
+
cwd: options.cwd
|
|
13906
|
+
};
|
|
13907
|
+
if (options.extra?.title !== void 0) meta.title = options.extra.title;
|
|
13908
|
+
if (options.extra?.firstUserMessage !== void 0) {
|
|
13909
|
+
meta.firstUserMessage = options.extra.firstUserMessage;
|
|
13910
|
+
}
|
|
13911
|
+
if (options.extra?.model !== void 0) meta.model = options.extra.model;
|
|
13912
|
+
if (options.extra?.provider !== void 0) meta.provider = options.extra.provider;
|
|
13913
|
+
if (options.extra?.outputStyle !== void 0) meta.outputStyle = options.extra.outputStyle;
|
|
13914
|
+
await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(meta, null, 2)}
|
|
13915
|
+
`);
|
|
13916
|
+
return { sessionDir, meta, created: true };
|
|
13917
|
+
}
|
|
13747
13918
|
|
|
13748
13919
|
// ../kernel/src/journal/lock.ts
|
|
13920
|
+
import { open as open2, rm as rm2, stat, readFile as readFile2 } from "node:fs/promises";
|
|
13921
|
+
import os2 from "node:os";
|
|
13749
13922
|
var DEFAULT_STALE_MS = 30 * 60 * 1e3;
|
|
13923
|
+
function defaultIsPidAlive(pid) {
|
|
13924
|
+
try {
|
|
13925
|
+
process.kill(pid, 0);
|
|
13926
|
+
return true;
|
|
13927
|
+
} catch (err) {
|
|
13928
|
+
if (isErrnoException(err) && err.code === "EPERM") return true;
|
|
13929
|
+
return false;
|
|
13930
|
+
}
|
|
13931
|
+
}
|
|
13932
|
+
async function readLockPayload(lockPath) {
|
|
13933
|
+
try {
|
|
13934
|
+
const raw = await readFile2(lockPath, "utf8");
|
|
13935
|
+
const parsed = JSON.parse(raw);
|
|
13936
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.pid === "number") {
|
|
13937
|
+
return parsed;
|
|
13938
|
+
}
|
|
13939
|
+
return null;
|
|
13940
|
+
} catch {
|
|
13941
|
+
return null;
|
|
13942
|
+
}
|
|
13943
|
+
}
|
|
13944
|
+
async function tryCreateLockFile(lockPath) {
|
|
13945
|
+
let handle;
|
|
13946
|
+
try {
|
|
13947
|
+
handle = await open2(lockPath, "wx");
|
|
13948
|
+
} catch (err) {
|
|
13949
|
+
if (isErrnoException(err) && err.code === "EEXIST") return false;
|
|
13950
|
+
throw err;
|
|
13951
|
+
}
|
|
13952
|
+
try {
|
|
13953
|
+
const payload = {
|
|
13954
|
+
pid: process.pid,
|
|
13955
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13956
|
+
host: os2.hostname()
|
|
13957
|
+
};
|
|
13958
|
+
await handle.writeFile(`${JSON.stringify(payload)}
|
|
13959
|
+
`, "utf8");
|
|
13960
|
+
await handle.sync();
|
|
13961
|
+
} finally {
|
|
13962
|
+
await handle.close();
|
|
13963
|
+
}
|
|
13964
|
+
return true;
|
|
13965
|
+
}
|
|
13966
|
+
async function acquireSessionLock(sessionDir, options = {}) {
|
|
13967
|
+
const lockPath = lockFilePath(sessionDir);
|
|
13968
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
13969
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
13970
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13971
|
+
if (await tryCreateLockFile(lockPath)) {
|
|
13972
|
+
let released = false;
|
|
13973
|
+
return {
|
|
13974
|
+
lockPath,
|
|
13975
|
+
async release() {
|
|
13976
|
+
if (released) return;
|
|
13977
|
+
released = true;
|
|
13978
|
+
await rm2(lockPath, { force: true });
|
|
13979
|
+
}
|
|
13980
|
+
};
|
|
13981
|
+
}
|
|
13982
|
+
const payload = await readLockPayload(lockPath);
|
|
13983
|
+
let mtimeMs = null;
|
|
13984
|
+
try {
|
|
13985
|
+
mtimeMs = (await stat(lockPath)).mtimeMs;
|
|
13986
|
+
} catch (err) {
|
|
13987
|
+
if (isErrnoException(err) && err.code === "ENOENT") {
|
|
13988
|
+
continue;
|
|
13989
|
+
}
|
|
13990
|
+
throw err;
|
|
13991
|
+
}
|
|
13992
|
+
const holderAlive = payload !== null && isPidAlive(payload.pid);
|
|
13993
|
+
const isStale = Date.now() - mtimeMs > staleMs;
|
|
13994
|
+
const canPreempt = payload === null ? isStale : !holderAlive || isStale;
|
|
13995
|
+
if (!canPreempt) {
|
|
13996
|
+
const holder = payload ? `pid=${payload.pid} host=${payload.host} since=${payload.startedAt}` : "未知持有者";
|
|
13997
|
+
throw new JournalError(
|
|
13998
|
+
"SESSION_LOCKED",
|
|
13999
|
+
`会话已被其他进程锁定(${holder}),锁文件:${lockPath}`
|
|
14000
|
+
);
|
|
14001
|
+
}
|
|
14002
|
+
await rm2(lockPath, { force: true });
|
|
14003
|
+
}
|
|
14004
|
+
throw new JournalError(
|
|
14005
|
+
"SESSION_LOCKED",
|
|
14006
|
+
`会话锁竞争激烈,清理陈旧锁后重试仍失败:${lockPath}`
|
|
14007
|
+
);
|
|
14008
|
+
}
|
|
14009
|
+
|
|
14010
|
+
// ../kernel/src/journal/reader.ts
|
|
14011
|
+
import { appendFile, readFile as readFile4, truncate } from "node:fs/promises";
|
|
14012
|
+
|
|
14013
|
+
// ../kernel/src/journal/attachments.ts
|
|
14014
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
14015
|
+
import { access, mkdir as mkdir2, readFile as readFile3 } from "node:fs/promises";
|
|
14016
|
+
import path6 from "node:path";
|
|
14017
|
+
var ATTACHMENTS_DIR_NAME = "attachments";
|
|
14018
|
+
var IMAGE_MESSAGE_KINDS = /* @__PURE__ */ new Set([
|
|
14019
|
+
"user_prompt",
|
|
14020
|
+
"assistant_message",
|
|
14021
|
+
"tool_result"
|
|
14022
|
+
]);
|
|
14023
|
+
var REF_PATTERN = /^sha256:([0-9a-f]{64})$/;
|
|
14024
|
+
function attachmentsDirPath(sessionDir) {
|
|
14025
|
+
return path6.join(sessionDir, ATTACHMENTS_DIR_NAME);
|
|
14026
|
+
}
|
|
14027
|
+
function attachmentFilePath(sessionDir, sha256Hex2) {
|
|
14028
|
+
return path6.join(attachmentsDirPath(sessionDir), sha256Hex2);
|
|
14029
|
+
}
|
|
14030
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14031
|
+
var isInlineImage = (b) => b["t"] === "image" && typeof b["data"] === "string" && typeof b["mime"] === "string" && b["$ref"] === void 0;
|
|
14032
|
+
var isExternalizedImage = (b) => b["t"] === "image" && typeof b["$ref"] === "string" && typeof b["mime"] === "string" && b["data"] === void 0;
|
|
14033
|
+
async function externalizeOne(block, sessionDir) {
|
|
14034
|
+
try {
|
|
14035
|
+
const bytes = Buffer.from(block["data"], "base64");
|
|
14036
|
+
const hex = createHash6("sha256").update(bytes).digest("hex");
|
|
14037
|
+
const file = attachmentFilePath(sessionDir, hex);
|
|
14038
|
+
let exists = true;
|
|
14039
|
+
try {
|
|
14040
|
+
await access(file);
|
|
14041
|
+
} catch {
|
|
14042
|
+
exists = false;
|
|
14043
|
+
}
|
|
14044
|
+
if (!exists) {
|
|
14045
|
+
await mkdir2(attachmentsDirPath(sessionDir), { recursive: true });
|
|
14046
|
+
await writeFileDurable(file, bytes);
|
|
14047
|
+
}
|
|
14048
|
+
const out = {
|
|
14049
|
+
t: "image",
|
|
14050
|
+
mime: block["mime"],
|
|
14051
|
+
$ref: `sha256:${hex}`,
|
|
14052
|
+
bytes: bytes.length
|
|
14053
|
+
};
|
|
14054
|
+
const dims = probeImageDimensions(bytes);
|
|
14055
|
+
if (dims !== null) {
|
|
14056
|
+
out["w"] = dims.width;
|
|
14057
|
+
out["h"] = dims.height;
|
|
14058
|
+
}
|
|
14059
|
+
return out;
|
|
14060
|
+
} catch {
|
|
14061
|
+
return block;
|
|
14062
|
+
}
|
|
14063
|
+
}
|
|
14064
|
+
async function rehydrateOne(block, sessionDir, notes) {
|
|
14065
|
+
const ref = block["$ref"];
|
|
14066
|
+
const placeholder = (noteReason, textReason) => {
|
|
14067
|
+
notes.push(`image 附件${noteReason},块已降级为占位文本(${ref})`);
|
|
14068
|
+
return { t: "text", text: `[image unavailable: attachment ${ref} ${textReason}]` };
|
|
14069
|
+
};
|
|
14070
|
+
const match = REF_PATTERN.exec(ref);
|
|
14071
|
+
if (match === null) return placeholder("引用非法", "has an invalid ref");
|
|
14072
|
+
const hex = match[1];
|
|
14073
|
+
let bytes;
|
|
14074
|
+
try {
|
|
14075
|
+
bytes = await readFile3(attachmentFilePath(sessionDir, hex));
|
|
14076
|
+
} catch {
|
|
14077
|
+
return placeholder("缺失", "is missing");
|
|
14078
|
+
}
|
|
14079
|
+
if (createHash6("sha256").update(bytes).digest("hex") !== hex) {
|
|
14080
|
+
return placeholder("摘要不符", "is corrupt (sha256 mismatch)");
|
|
14081
|
+
}
|
|
14082
|
+
return { t: "image", mime: block["mime"], data: bytes.toString("base64") };
|
|
14083
|
+
}
|
|
14084
|
+
async function mapImageBlocks(payload, match, mapper) {
|
|
14085
|
+
if (!isRecord(payload) || !Array.isArray(payload["blocks"])) return payload;
|
|
14086
|
+
let changed = false;
|
|
14087
|
+
const blocks = [];
|
|
14088
|
+
for (const raw of payload["blocks"]) {
|
|
14089
|
+
if (!isRecord(raw)) {
|
|
14090
|
+
blocks.push(raw);
|
|
14091
|
+
continue;
|
|
14092
|
+
}
|
|
14093
|
+
if (match(raw)) {
|
|
14094
|
+
const mapped = await mapper(raw);
|
|
14095
|
+
if (mapped !== raw) changed = true;
|
|
14096
|
+
blocks.push(mapped);
|
|
14097
|
+
continue;
|
|
14098
|
+
}
|
|
14099
|
+
if (raw["t"] === "tool_result" && Array.isArray(raw["content"])) {
|
|
14100
|
+
let itemChanged = false;
|
|
14101
|
+
const content = [];
|
|
14102
|
+
for (const item of raw["content"]) {
|
|
14103
|
+
if (!isRecord(item) || !match(item)) {
|
|
14104
|
+
content.push(item);
|
|
14105
|
+
continue;
|
|
14106
|
+
}
|
|
14107
|
+
const mapped = await mapper(item);
|
|
14108
|
+
if (mapped !== item) itemChanged = true;
|
|
14109
|
+
content.push(mapped);
|
|
14110
|
+
}
|
|
14111
|
+
if (!itemChanged) {
|
|
14112
|
+
blocks.push(raw);
|
|
14113
|
+
continue;
|
|
14114
|
+
}
|
|
14115
|
+
changed = true;
|
|
14116
|
+
blocks.push({ ...raw, content });
|
|
14117
|
+
continue;
|
|
14118
|
+
}
|
|
14119
|
+
blocks.push(raw);
|
|
14120
|
+
}
|
|
14121
|
+
return changed ? { ...payload, blocks } : payload;
|
|
14122
|
+
}
|
|
14123
|
+
async function externalizeImagePayload(payload, sessionDir) {
|
|
14124
|
+
return mapImageBlocks(payload, isInlineImage, (b) => externalizeOne(b, sessionDir));
|
|
14125
|
+
}
|
|
14126
|
+
async function rehydrateImagePayload(payload, sessionDir, notes) {
|
|
14127
|
+
return mapImageBlocks(payload, isExternalizedImage, (b) => rehydrateOne(b, sessionDir, notes));
|
|
14128
|
+
}
|
|
14129
|
+
|
|
14130
|
+
// ../kernel/src/journal/reader.ts
|
|
14131
|
+
var MAIN_BRANCH_ID = "main";
|
|
14132
|
+
var NEWLINE_BYTE = 10;
|
|
14133
|
+
async function readAll(sessionDir, options = {}) {
|
|
14134
|
+
const filePath = journalFilePath(sessionDir);
|
|
14135
|
+
let buf;
|
|
14136
|
+
try {
|
|
14137
|
+
buf = await readFile4(filePath);
|
|
14138
|
+
} catch (err) {
|
|
14139
|
+
if (isErrnoException(err) && err.code === "ENOENT") return { records: [], repairs: [] };
|
|
14140
|
+
throw err;
|
|
14141
|
+
}
|
|
14142
|
+
const records = [];
|
|
14143
|
+
const lineStartBytes = [];
|
|
14144
|
+
const repairs = [];
|
|
14145
|
+
let truncateTo = null;
|
|
14146
|
+
let missingFinalNewline = false;
|
|
14147
|
+
let pos = 0;
|
|
14148
|
+
let lineNo = 0;
|
|
14149
|
+
while (pos < buf.length) {
|
|
14150
|
+
lineNo++;
|
|
14151
|
+
const nl = buf.indexOf(NEWLINE_BYTE, pos);
|
|
14152
|
+
const hasNewline = nl !== -1;
|
|
14153
|
+
const end = hasNewline ? nl : buf.length;
|
|
14154
|
+
const isLastLine = !hasNewline || nl === buf.length - 1;
|
|
14155
|
+
const text = buf.subarray(pos, end).toString("utf8");
|
|
14156
|
+
let parsed;
|
|
14157
|
+
try {
|
|
14158
|
+
parsed = JSON.parse(text);
|
|
14159
|
+
} catch {
|
|
14160
|
+
if (isLastLine) {
|
|
14161
|
+
truncateTo = pos;
|
|
14162
|
+
repairs.push(`截断崩溃残留的不完整尾行(第 ${lineNo} 行,${buf.length - pos} 字节)`);
|
|
14163
|
+
break;
|
|
14164
|
+
}
|
|
14165
|
+
throw new JournalCorruptionError(lineNo, "JSON 解析失败且非最后一行,无法用崩溃截断解释");
|
|
14166
|
+
}
|
|
14167
|
+
const result = JournalRecordSchema.safeParse(parsed);
|
|
14168
|
+
if (!result.success) {
|
|
14169
|
+
throw new JournalCorruptionError(lineNo, `不符合 JournalRecord schema:${result.error.message}`);
|
|
14170
|
+
}
|
|
14171
|
+
records.push(result.data);
|
|
14172
|
+
lineStartBytes.push(pos);
|
|
14173
|
+
if (!hasNewline) missingFinalNewline = true;
|
|
14174
|
+
pos = end + 1;
|
|
14175
|
+
}
|
|
14176
|
+
while (records.length > 0 && records[records.length - 1].completeness === "partial") {
|
|
14177
|
+
const idx = records.length - 1;
|
|
14178
|
+
truncateTo = lineStartBytes[idx];
|
|
14179
|
+
repairs.push(`截断崩溃残留的 partial 尾记录(seq=${records[idx].seq})`);
|
|
14180
|
+
records.pop();
|
|
14181
|
+
lineStartBytes.pop();
|
|
14182
|
+
missingFinalNewline = false;
|
|
14183
|
+
}
|
|
14184
|
+
if (truncateTo !== null) {
|
|
14185
|
+
await truncate(filePath, truncateTo);
|
|
14186
|
+
} else if (missingFinalNewline) {
|
|
14187
|
+
await appendFile(filePath, "\n");
|
|
14188
|
+
repairs.push("补齐末行缺失的换行符");
|
|
14189
|
+
}
|
|
14190
|
+
let integrityBreakAt;
|
|
14191
|
+
let expectedPrev = null;
|
|
14192
|
+
for (let i = 0; i < records.length; i++) {
|
|
14193
|
+
const rec = records[i];
|
|
14194
|
+
if (rec.integrity.previousHash !== expectedPrev || computeRecordHash(rec) !== rec.integrity.hash) {
|
|
14195
|
+
integrityBreakAt = i;
|
|
14196
|
+
break;
|
|
14197
|
+
}
|
|
14198
|
+
expectedPrev = rec.integrity.hash;
|
|
14199
|
+
}
|
|
14200
|
+
if (options.rehydrateImages !== false) {
|
|
14201
|
+
for (let i = 0; i < records.length; i++) {
|
|
14202
|
+
const rec = records[i];
|
|
14203
|
+
if (!IMAGE_MESSAGE_KINDS.has(rec.kind)) continue;
|
|
14204
|
+
const notes = [];
|
|
14205
|
+
const payload = await rehydrateImagePayload(rec.payload, sessionDir, notes);
|
|
14206
|
+
if (payload !== rec.payload) records[i] = { ...rec, payload };
|
|
14207
|
+
for (const note of notes) repairs.push(`seq=${rec.seq}:${note}`);
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
return integrityBreakAt === void 0 ? { records, repairs } : { records, integrityBreakAt, repairs };
|
|
14211
|
+
}
|
|
14212
|
+
var MESSAGE_KINDS = /* @__PURE__ */ new Set([
|
|
14213
|
+
"user_prompt",
|
|
14214
|
+
"assistant_message",
|
|
14215
|
+
"tool_result"
|
|
14216
|
+
]);
|
|
14217
|
+
function rebuildState(records) {
|
|
14218
|
+
const messages = [];
|
|
14219
|
+
const warnings = [];
|
|
14220
|
+
for (const rec of records) {
|
|
14221
|
+
if (rec.branchId !== MAIN_BRANCH_ID) continue;
|
|
14222
|
+
if (!MESSAGE_KINDS.has(rec.kind)) continue;
|
|
14223
|
+
if (!rec.visibility.model) continue;
|
|
14224
|
+
const parsed = IRMessageSchema.safeParse(rec.payload);
|
|
14225
|
+
if (!parsed.success) {
|
|
14226
|
+
warnings.push(`seq=${rec.seq}(kind=${rec.kind})的 payload 不是合法 IRMessage,已跳过`);
|
|
14227
|
+
continue;
|
|
14228
|
+
}
|
|
14229
|
+
messages.push(parsed.data);
|
|
14230
|
+
}
|
|
14231
|
+
const last = records.length > 0 ? records[records.length - 1] : void 0;
|
|
14232
|
+
return { messages, lastSeq: last?.seq ?? -1, branchId: MAIN_BRANCH_ID, warnings };
|
|
14233
|
+
}
|
|
14234
|
+
|
|
14235
|
+
// ../kernel/src/journal/writer.ts
|
|
14236
|
+
import { open as open3 } from "node:fs/promises";
|
|
14237
|
+
var CRITICAL_KINDS = /* @__PURE__ */ new Set([
|
|
14238
|
+
"user_prompt",
|
|
14239
|
+
"assistant_message",
|
|
14240
|
+
"tool_result",
|
|
14241
|
+
"fork",
|
|
14242
|
+
"compact_boundary",
|
|
14243
|
+
"queue_enqueue",
|
|
14244
|
+
"queue_ack"
|
|
14245
|
+
]);
|
|
14246
|
+
var JournalWriter = class _JournalWriter {
|
|
14247
|
+
#handle;
|
|
14248
|
+
/** 串行化队列尾:并发 append 按调用序依次执行 */
|
|
14249
|
+
#queue = Promise.resolve();
|
|
14250
|
+
#previousHash;
|
|
14251
|
+
#nextSeq;
|
|
14252
|
+
/** 已确认成功的文件字节长度,写失败时截断回滚的目标 */
|
|
14253
|
+
#byteLength;
|
|
14254
|
+
/** 会话目录(J15 附件外置的落点根) */
|
|
14255
|
+
#sessionDir;
|
|
14256
|
+
#closed = false;
|
|
14257
|
+
/** 回滚失败后置真:磁盘与内存链状态已不可信,拒绝继续写 */
|
|
14258
|
+
#broken = false;
|
|
14259
|
+
constructor(handle, previousHash, nextSeq, byteLength, sessionDir) {
|
|
14260
|
+
this.#handle = handle;
|
|
14261
|
+
this.#previousHash = previousHash;
|
|
14262
|
+
this.#nextSeq = nextSeq;
|
|
14263
|
+
this.#byteLength = byteLength;
|
|
14264
|
+
this.#sessionDir = sessionDir;
|
|
14265
|
+
}
|
|
14266
|
+
/**
|
|
14267
|
+
* 打开(或创建)会话 journal 并定位恢复点:经 readAll 读全量(顺带截断
|
|
14268
|
+
* 崩溃残留、保证行边界干净),取末条记录的 seq/hash 接续哈希链。
|
|
14269
|
+
* 恢复点读取用原始落盘形态(rehydrateImages:false)——只取 seq/hash,
|
|
14270
|
+
* 不为附件回填付费。前置条件:会话目录已存在(经 ensureSessionDir);
|
|
14271
|
+
* 会话锁由调用方持有。
|
|
14272
|
+
*/
|
|
14273
|
+
static async open(sessionDir) {
|
|
14274
|
+
const { records } = await readAll(sessionDir, { rehydrateImages: false });
|
|
14275
|
+
const last = records.length > 0 ? records[records.length - 1] : void 0;
|
|
14276
|
+
const handle = await open3(journalFilePath(sessionDir), "a");
|
|
14277
|
+
try {
|
|
14278
|
+
const { size } = await handle.stat();
|
|
14279
|
+
return new _JournalWriter(handle, last?.integrity.hash ?? null, (last?.seq ?? -1) + 1, size, sessionDir);
|
|
14280
|
+
} catch (err) {
|
|
14281
|
+
await handle.close();
|
|
14282
|
+
throw err;
|
|
14283
|
+
}
|
|
14284
|
+
}
|
|
14285
|
+
/**
|
|
14286
|
+
* 追加一条记录(schema 校验后写);返回实际落盘的完整记录。
|
|
14287
|
+
* 并发调用按发起顺序串行落盘,seq 与文件行序一致。
|
|
14288
|
+
*/
|
|
14289
|
+
append(input) {
|
|
14290
|
+
const task = this.#queue.then(() => this.#appendSerialized(input));
|
|
14291
|
+
this.#queue = task.then(
|
|
14292
|
+
() => void 0,
|
|
14293
|
+
() => void 0
|
|
14294
|
+
);
|
|
14295
|
+
return task;
|
|
14296
|
+
}
|
|
14297
|
+
async #appendSerialized(input) {
|
|
14298
|
+
if (this.#closed) {
|
|
14299
|
+
throw new JournalError("JOURNAL_CLOSED", "JournalWriter 已关闭,不能继续 append");
|
|
14300
|
+
}
|
|
14301
|
+
if (this.#broken) {
|
|
14302
|
+
throw new JournalError("JOURNAL_BROKEN", "此前写失败且截断回滚未成功,writer 已进入不可写状态");
|
|
14303
|
+
}
|
|
14304
|
+
let effective = input;
|
|
14305
|
+
if (IMAGE_MESSAGE_KINDS.has(input.kind)) {
|
|
14306
|
+
const payload = await externalizeImagePayload(input.payload, this.#sessionDir);
|
|
14307
|
+
if (payload !== input.payload) effective = { ...input, payload };
|
|
14308
|
+
}
|
|
14309
|
+
const candidate = {
|
|
14310
|
+
...effective,
|
|
14311
|
+
schemaVersion: 1,
|
|
14312
|
+
seq: this.#nextSeq,
|
|
14313
|
+
completeness: "complete",
|
|
14314
|
+
integrity: { previousHash: this.#previousHash, hash: "" }
|
|
14315
|
+
};
|
|
14316
|
+
const parsed = JournalRecordSchema.safeParse(candidate);
|
|
14317
|
+
if (!parsed.success) {
|
|
14318
|
+
throw new JournalError("RECORD_INVALID", `记录不符合 JournalRecord schema:${parsed.error.message}`);
|
|
14319
|
+
}
|
|
14320
|
+
const record = parsed.data;
|
|
14321
|
+
record.integrity.hash = computeRecordHash(record);
|
|
14322
|
+
const line = Buffer.from(`${JSON.stringify(record)}
|
|
14323
|
+
`, "utf8");
|
|
14324
|
+
try {
|
|
14325
|
+
let written = 0;
|
|
14326
|
+
while (written < line.length) {
|
|
14327
|
+
const { bytesWritten } = await this.#handle.write(line, written, line.length - written);
|
|
14328
|
+
written += bytesWritten;
|
|
14329
|
+
}
|
|
14330
|
+
if (CRITICAL_KINDS.has(record.kind)) {
|
|
14331
|
+
try {
|
|
14332
|
+
await this.#handle.datasync();
|
|
14333
|
+
} catch {
|
|
14334
|
+
await this.#handle.sync();
|
|
14335
|
+
}
|
|
14336
|
+
}
|
|
14337
|
+
} catch (err) {
|
|
14338
|
+
try {
|
|
14339
|
+
await this.#handle.truncate(this.#byteLength);
|
|
14340
|
+
} catch {
|
|
14341
|
+
this.#broken = true;
|
|
14342
|
+
}
|
|
14343
|
+
throw err;
|
|
14344
|
+
}
|
|
14345
|
+
this.#byteLength += line.length;
|
|
14346
|
+
this.#previousHash = record.integrity.hash;
|
|
14347
|
+
this.#nextSeq = record.seq + 1;
|
|
14348
|
+
return record;
|
|
14349
|
+
}
|
|
14350
|
+
/** 下一条将分配的 seq(诊断用) */
|
|
14351
|
+
get nextSeq() {
|
|
14352
|
+
return this.#nextSeq;
|
|
14353
|
+
}
|
|
14354
|
+
/** 等待队列排空并关闭文件句柄;幂等 */
|
|
14355
|
+
async close() {
|
|
14356
|
+
if (this.#closed) return;
|
|
14357
|
+
this.#closed = true;
|
|
14358
|
+
await this.#queue;
|
|
14359
|
+
await this.#handle.close();
|
|
14360
|
+
}
|
|
14361
|
+
};
|
|
13750
14362
|
|
|
13751
14363
|
// ../kernel/src/tools/search/path-utils.ts
|
|
13752
|
-
import * as
|
|
14364
|
+
import * as path7 from "node:path";
|
|
13753
14365
|
function toPosixPath(p) {
|
|
13754
14366
|
return p.replaceAll("\\", "/");
|
|
13755
14367
|
}
|
|
@@ -13757,10 +14369,10 @@ function normalizeDriveLetter(p) {
|
|
|
13757
14369
|
return /^[A-Za-z]:/.test(p) ? p.charAt(0).toLowerCase() + p.slice(1) : p;
|
|
13758
14370
|
}
|
|
13759
14371
|
function isAbsolutePath(p) {
|
|
13760
|
-
return
|
|
14372
|
+
return path7.win32.isAbsolute(p) || path7.posix.isAbsolute(p);
|
|
13761
14373
|
}
|
|
13762
14374
|
function canonicalize(p) {
|
|
13763
|
-
return normalizeDriveLetter(toPosixPath(
|
|
14375
|
+
return normalizeDriveLetter(toPosixPath(path7.resolve(p)));
|
|
13764
14376
|
}
|
|
13765
14377
|
function resolvePathArg(p, cwd) {
|
|
13766
14378
|
const target = p ?? cwd;
|
|
@@ -13813,11 +14425,11 @@ function expandBraces(pattern) {
|
|
|
13813
14425
|
return out;
|
|
13814
14426
|
}
|
|
13815
14427
|
function expandOnce(pattern) {
|
|
13816
|
-
const
|
|
13817
|
-
if (
|
|
14428
|
+
const open4 = pattern.indexOf("{");
|
|
14429
|
+
if (open4 === -1) return null;
|
|
13818
14430
|
let depth = 0;
|
|
13819
14431
|
let close = -1;
|
|
13820
|
-
for (let i =
|
|
14432
|
+
for (let i = open4; i < pattern.length; i++) {
|
|
13821
14433
|
const ch = pattern.charAt(i);
|
|
13822
14434
|
if (ch === "{") depth += 1;
|
|
13823
14435
|
else if (ch === "}") {
|
|
@@ -13829,8 +14441,8 @@ function expandOnce(pattern) {
|
|
|
13829
14441
|
}
|
|
13830
14442
|
}
|
|
13831
14443
|
if (close === -1) return null;
|
|
13832
|
-
const prefix = pattern.slice(0,
|
|
13833
|
-
const body = pattern.slice(
|
|
14444
|
+
const prefix = pattern.slice(0, open4);
|
|
14445
|
+
const body = pattern.slice(open4 + 1, close);
|
|
13834
14446
|
const suffix = pattern.slice(close + 1);
|
|
13835
14447
|
const alternatives = [];
|
|
13836
14448
|
let level = 0;
|
|
@@ -14142,7 +14754,7 @@ var SESSION_EVENT_VOCABULARY = {
|
|
|
14142
14754
|
var SESSION_EVENT_KINDS = Object.keys(SESSION_EVENT_VOCABULARY);
|
|
14143
14755
|
|
|
14144
14756
|
// ../kernel/src/tools/files/read.ts
|
|
14145
|
-
import
|
|
14757
|
+
import path8 from "node:path";
|
|
14146
14758
|
import { z as z15 } from "zod";
|
|
14147
14759
|
|
|
14148
14760
|
// ../kernel/src/tools/files/encoding.ts
|
|
@@ -14224,19 +14836,19 @@ function truncateLine(line) {
|
|
|
14224
14836
|
return `${line.slice(0, cut)}…[line truncated: ${line.length} chars total; use Grep to inspect the rest]`;
|
|
14225
14837
|
}
|
|
14226
14838
|
async function findSimilarFiles(fs3, filePath) {
|
|
14227
|
-
const dir =
|
|
14228
|
-
const targetBase =
|
|
14229
|
-
const targetName =
|
|
14839
|
+
const dir = path8.dirname(filePath);
|
|
14840
|
+
const targetBase = path8.basename(filePath);
|
|
14841
|
+
const targetName = path8.parse(filePath).name.toLowerCase();
|
|
14230
14842
|
try {
|
|
14231
14843
|
const entries = await fs3.readdir(dir);
|
|
14232
14844
|
return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).filter(
|
|
14233
|
-
(name) => name.toLowerCase() !== targetBase.toLowerCase() &&
|
|
14234
|
-
).slice(0, 3).map((name) =>
|
|
14845
|
+
(name) => name.toLowerCase() !== targetBase.toLowerCase() && path8.parse(name).name.toLowerCase() === targetName
|
|
14846
|
+
).slice(0, 3).map((name) => path8.join(dir, name));
|
|
14235
14847
|
} catch {
|
|
14236
14848
|
return [];
|
|
14237
14849
|
}
|
|
14238
14850
|
}
|
|
14239
|
-
async function executeImageRead(resolved, args, ctx, fs3,
|
|
14851
|
+
async function executeImageRead(resolved, args, ctx, fs3, stat2, options) {
|
|
14240
14852
|
let mode;
|
|
14241
14853
|
try {
|
|
14242
14854
|
mode = options.imageInput?.();
|
|
@@ -14255,9 +14867,9 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14255
14867
|
"offset/limit apply to text files only. Call Read again without offset/limit to read this image file."
|
|
14256
14868
|
);
|
|
14257
14869
|
}
|
|
14258
|
-
if (
|
|
14870
|
+
if (stat2.size > READ_IMAGE_RAW_MAX_BYTES) {
|
|
14259
14871
|
return errorResult(
|
|
14260
|
-
`Image file is ${
|
|
14872
|
+
`Image file is ${stat2.size} bytes, which exceeds the per-image limit of ${READ_IMAGE_RAW_MAX_BYTES} bytes (5 MiB base64-encoded, aligned with the platform cap). Downscale or compress the image, then retry.`
|
|
14261
14873
|
);
|
|
14262
14874
|
}
|
|
14263
14875
|
let buf;
|
|
@@ -14272,10 +14884,10 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14272
14884
|
const probed = probeImage(buf);
|
|
14273
14885
|
if (probed === null) {
|
|
14274
14886
|
return errorResult(
|
|
14275
|
-
`File has an image extension but its content is not a readable PNG/JPEG/GIF/WebP image (bad or truncated header): ${
|
|
14887
|
+
`File has an image extension but its content is not a readable PNG/JPEG/GIF/WebP image (bad or truncated header): ${path8.basename(resolved)}. If it is actually a text file, rename it; otherwise re-export the image and retry.`
|
|
14276
14888
|
);
|
|
14277
14889
|
}
|
|
14278
|
-
registerFileRead(ctx, normalizeFileKey(resolved),
|
|
14890
|
+
registerFileRead(ctx, normalizeFileKey(resolved), stat2.mtimeMs);
|
|
14279
14891
|
const data = {
|
|
14280
14892
|
path: resolved,
|
|
14281
14893
|
mime: probed.mime,
|
|
@@ -14283,7 +14895,7 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14283
14895
|
height: probed.height,
|
|
14284
14896
|
bytes: buf.length
|
|
14285
14897
|
};
|
|
14286
|
-
const meta = `Image file: ${
|
|
14898
|
+
const meta = `Image file: ${path8.basename(resolved)}
|
|
14287
14899
|
Format: ${probed.mime}
|
|
14288
14900
|
Dimensions: ${probed.width}x${probed.height} px
|
|
14289
14901
|
Size: ${buf.length} bytes`;
|
|
@@ -14306,8 +14918,8 @@ function createReadTool(options = {}) {
|
|
|
14306
14918
|
isConcurrencySafe: true,
|
|
14307
14919
|
touchedPathsOf(args) {
|
|
14308
14920
|
const parsed = ReadArgsSchema.safeParse(args);
|
|
14309
|
-
if (!parsed.success || !
|
|
14310
|
-
return [
|
|
14921
|
+
if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
|
|
14922
|
+
return [path8.resolve(parsed.data.file_path)];
|
|
14311
14923
|
},
|
|
14312
14924
|
async execute(args, ctx) {
|
|
14313
14925
|
if (ctx.signal.aborted) {
|
|
@@ -14321,15 +14933,15 @@ function createReadTool(options = {}) {
|
|
|
14321
14933
|
if (invalid) {
|
|
14322
14934
|
return invalid;
|
|
14323
14935
|
}
|
|
14324
|
-
const resolved =
|
|
14936
|
+
const resolved = path8.resolve(args.file_path);
|
|
14325
14937
|
const fs3 = fsOf(ctx);
|
|
14326
14938
|
const realTarget = await checkRealTarget("Read", resolved, { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
14327
14939
|
if (!realTarget.ok) {
|
|
14328
14940
|
return errorResult(realTarget.reason);
|
|
14329
14941
|
}
|
|
14330
|
-
let
|
|
14942
|
+
let stat2;
|
|
14331
14943
|
try {
|
|
14332
|
-
|
|
14944
|
+
stat2 = await fs3.stat(resolved);
|
|
14333
14945
|
} catch (err) {
|
|
14334
14946
|
if (fsErrorCode(err) === "ENOENT") {
|
|
14335
14947
|
const similar = await findSimilarFiles(fs3, resolved);
|
|
@@ -14338,11 +14950,11 @@ function createReadTool(options = {}) {
|
|
|
14338
14950
|
}
|
|
14339
14951
|
return errorResult(`Failed to read file: ${errorMessageOf(err)}`);
|
|
14340
14952
|
}
|
|
14341
|
-
if (
|
|
14953
|
+
if (stat2.isDirectory()) {
|
|
14342
14954
|
return errorResult(`Path is a directory, not a file: ${resolved}.`);
|
|
14343
14955
|
}
|
|
14344
|
-
if (isImageFileExtension(
|
|
14345
|
-
return executeImageRead(resolved, args, ctx, fs3,
|
|
14956
|
+
if (isImageFileExtension(path8.extname(resolved))) {
|
|
14957
|
+
return executeImageRead(resolved, args, ctx, fs3, stat2, options);
|
|
14346
14958
|
}
|
|
14347
14959
|
const fileKey = normalizeFileKey(resolved);
|
|
14348
14960
|
const effectiveOffset = args.offset ?? 1;
|
|
@@ -14350,7 +14962,7 @@ function createReadTool(options = {}) {
|
|
|
14350
14962
|
const dupWindow = takeDuplicateReadWindow(
|
|
14351
14963
|
ctx,
|
|
14352
14964
|
fileKey,
|
|
14353
|
-
|
|
14965
|
+
stat2.mtimeMs,
|
|
14354
14966
|
effectiveOffset,
|
|
14355
14967
|
effectiveLimit
|
|
14356
14968
|
);
|
|
@@ -14367,9 +14979,9 @@ function createReadTool(options = {}) {
|
|
|
14367
14979
|
data2
|
|
14368
14980
|
);
|
|
14369
14981
|
}
|
|
14370
|
-
if (
|
|
14982
|
+
if (stat2.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
|
|
14371
14983
|
return errorResult(
|
|
14372
|
-
`File is ${
|
|
14984
|
+
`File is ${stat2.size} bytes, which exceeds the ${READ_MAX_FULL_BYTES}-byte limit for reading the whole file at once (large files inflate context: this one would be roughly ${Math.ceil(stat2.size / 4)}+ tokens). Use the offset and limit parameters to read it in pages, or use Grep to locate the relevant sections first.`
|
|
14373
14985
|
);
|
|
14374
14986
|
}
|
|
14375
14987
|
let buf;
|
|
@@ -14388,7 +15000,7 @@ function createReadTool(options = {}) {
|
|
|
14388
15000
|
);
|
|
14389
15001
|
}
|
|
14390
15002
|
if (decoded.text.length === 0) {
|
|
14391
|
-
registerFileRead(ctx, fileKey,
|
|
15003
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14392
15004
|
offset: effectiveOffset,
|
|
14393
15005
|
limit: effectiveLimit,
|
|
14394
15006
|
totalLines: 0,
|
|
@@ -14402,7 +15014,7 @@ function createReadTool(options = {}) {
|
|
|
14402
15014
|
const startLine = effectiveOffset;
|
|
14403
15015
|
const maxLines = effectiveLimit;
|
|
14404
15016
|
if (startLine > totalLines) {
|
|
14405
|
-
registerFileRead(ctx, fileKey,
|
|
15017
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14406
15018
|
offset: effectiveOffset,
|
|
14407
15019
|
limit: effectiveLimit,
|
|
14408
15020
|
totalLines,
|
|
@@ -14421,7 +15033,7 @@ function createReadTool(options = {}) {
|
|
|
14421
15033
|
text += `
|
|
14422
15034
|
… (showing lines ${startLine}-${endLine} of ${totalLines}; use offset=${endLine + 1} to continue)`;
|
|
14423
15035
|
}
|
|
14424
|
-
registerFileRead(ctx, fileKey,
|
|
15036
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14425
15037
|
offset: effectiveOffset,
|
|
14426
15038
|
limit: effectiveLimit,
|
|
14427
15039
|
totalLines,
|
|
@@ -14435,7 +15047,7 @@ function createReadTool(options = {}) {
|
|
|
14435
15047
|
var ReadTool = createReadTool();
|
|
14436
15048
|
|
|
14437
15049
|
// ../kernel/src/tools/files/write.ts
|
|
14438
|
-
import
|
|
15050
|
+
import path9 from "node:path";
|
|
14439
15051
|
import { z as z16 } from "zod";
|
|
14440
15052
|
var WriteArgsSchema = z16.object({
|
|
14441
15053
|
file_path: z16.string().describe("Absolute path to the file to write. Relative paths are rejected."),
|
|
@@ -14454,8 +15066,8 @@ var WriteTool = {
|
|
|
14454
15066
|
isConcurrencySafe: false,
|
|
14455
15067
|
mutatedPathsOf(args) {
|
|
14456
15068
|
const parsed = WriteArgsSchema.safeParse(args);
|
|
14457
|
-
if (!parsed.success || !
|
|
14458
|
-
return [
|
|
15069
|
+
if (!parsed.success || !path9.isAbsolute(parsed.data.file_path)) return [];
|
|
15070
|
+
return [path9.resolve(parsed.data.file_path)];
|
|
14459
15071
|
},
|
|
14460
15072
|
async execute(args, ctx) {
|
|
14461
15073
|
if (ctx.signal.aborted) {
|
|
@@ -14469,7 +15081,7 @@ var WriteTool = {
|
|
|
14469
15081
|
if (invalid) {
|
|
14470
15082
|
return invalid;
|
|
14471
15083
|
}
|
|
14472
|
-
const resolved =
|
|
15084
|
+
const resolved = path9.resolve(args.file_path);
|
|
14473
15085
|
const key2 = normalizeFileKey(resolved);
|
|
14474
15086
|
const fs3 = fsOf(ctx);
|
|
14475
15087
|
const realTarget = await checkRealTarget("Write", resolved, {
|
|
@@ -14511,7 +15123,7 @@ var WriteTool = {
|
|
|
14511
15123
|
}
|
|
14512
15124
|
} else {
|
|
14513
15125
|
try {
|
|
14514
|
-
await fs3.mkdir(
|
|
15126
|
+
await fs3.mkdir(path9.dirname(resolved), { recursive: true });
|
|
14515
15127
|
} catch (err) {
|
|
14516
15128
|
return errorResult(`Failed to create parent directories: ${errorMessageOf(err)}`);
|
|
14517
15129
|
}
|
|
@@ -14567,7 +15179,7 @@ ${memoryNearLimitNote(health)}`, data);
|
|
|
14567
15179
|
};
|
|
14568
15180
|
|
|
14569
15181
|
// ../kernel/src/tools/files/edit.ts
|
|
14570
|
-
import
|
|
15182
|
+
import path10 from "node:path";
|
|
14571
15183
|
import { z as z17 } from "zod";
|
|
14572
15184
|
var EditArgsSchema = z17.object({
|
|
14573
15185
|
file_path: z17.string().describe("Absolute path to the file to edit. Relative paths are rejected."),
|
|
@@ -14610,8 +15222,8 @@ var EditTool = {
|
|
|
14610
15222
|
isConcurrencySafe: false,
|
|
14611
15223
|
mutatedPathsOf(args) {
|
|
14612
15224
|
const parsed = EditArgsSchema.safeParse(args);
|
|
14613
|
-
if (!parsed.success || !
|
|
14614
|
-
return [
|
|
15225
|
+
if (!parsed.success || !path10.isAbsolute(parsed.data.file_path)) return [];
|
|
15226
|
+
return [path10.resolve(parsed.data.file_path)];
|
|
14615
15227
|
},
|
|
14616
15228
|
async execute(args, ctx) {
|
|
14617
15229
|
if (ctx.signal.aborted) {
|
|
@@ -14625,7 +15237,7 @@ var EditTool = {
|
|
|
14625
15237
|
if (invalid) {
|
|
14626
15238
|
return invalid;
|
|
14627
15239
|
}
|
|
14628
|
-
const resolved =
|
|
15240
|
+
const resolved = path10.resolve(args.file_path);
|
|
14629
15241
|
const key2 = normalizeFileKey(resolved);
|
|
14630
15242
|
const fs3 = fsOf(ctx);
|
|
14631
15243
|
const realTarget = await checkRealTarget("Edit", resolved, {
|
|
@@ -14636,19 +15248,19 @@ var EditTool = {
|
|
|
14636
15248
|
if (!realTarget.ok) {
|
|
14637
15249
|
return errorResult(realTarget.reason);
|
|
14638
15250
|
}
|
|
14639
|
-
let
|
|
15251
|
+
let stat2;
|
|
14640
15252
|
try {
|
|
14641
|
-
|
|
15253
|
+
stat2 = await fs3.stat(resolved);
|
|
14642
15254
|
} catch (err) {
|
|
14643
15255
|
if (fsErrorCode(err) === "ENOENT") {
|
|
14644
15256
|
return errorResult(`File does not exist: ${resolved}. Use the Write tool to create a new file.`);
|
|
14645
15257
|
}
|
|
14646
15258
|
return errorResult(`Failed to access file: ${errorMessageOf(err)}`);
|
|
14647
15259
|
}
|
|
14648
|
-
if (
|
|
15260
|
+
if (stat2.isDirectory()) {
|
|
14649
15261
|
return errorResult(`Path is a directory, not a file: ${resolved}.`);
|
|
14650
15262
|
}
|
|
14651
|
-
const guard = checkStaleWriteGuard(ctx, key2,
|
|
15263
|
+
const guard = checkStaleWriteGuard(ctx, key2, stat2.mtimeMs, "editing");
|
|
14652
15264
|
if (guard) {
|
|
14653
15265
|
return guard;
|
|
14654
15266
|
}
|
|
@@ -14753,7 +15365,7 @@ ${memoryNearLimitNote(health)}`, data);
|
|
|
14753
15365
|
|
|
14754
15366
|
// ../kernel/src/tools/search/glob-tool.ts
|
|
14755
15367
|
import { z as z18 } from "zod";
|
|
14756
|
-
import
|
|
15368
|
+
import path11 from "node:path";
|
|
14757
15369
|
|
|
14758
15370
|
// ../kernel/src/tools/search/walker.ts
|
|
14759
15371
|
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -14847,7 +15459,7 @@ var globTool = {
|
|
|
14847
15459
|
return errorResult3(`path does not exist: ${root}`);
|
|
14848
15460
|
}
|
|
14849
15461
|
if (!rootStat.isDirectory()) return errorResult3(`path is not a directory: ${root}`);
|
|
14850
|
-
const realRoot = await checkRealTarget("Glob",
|
|
15462
|
+
const realRoot = await checkRealTarget("Glob", path11.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
14851
15463
|
if (!realRoot.ok) return errorResult3(realRoot.reason);
|
|
14852
15464
|
let matcher;
|
|
14853
15465
|
try {
|
|
@@ -14886,7 +15498,7 @@ var globTool = {
|
|
|
14886
15498
|
|
|
14887
15499
|
// ../kernel/src/tools/search/grep-tool.ts
|
|
14888
15500
|
import { z as z19 } from "zod";
|
|
14889
|
-
import
|
|
15501
|
+
import path12 from "node:path";
|
|
14890
15502
|
|
|
14891
15503
|
// ../kernel/src/tools/search/js-engine.ts
|
|
14892
15504
|
var GREP_MAX_FILE_SIZE = 4 * 1024 * 1024;
|
|
@@ -15360,7 +15972,7 @@ var grepTool = {
|
|
|
15360
15972
|
return finish(errorResult4(`path does not exist: ${root}`));
|
|
15361
15973
|
}
|
|
15362
15974
|
if (!rootStat.isDirectory()) return finish(errorResult4(`path is not a directory: ${root}`));
|
|
15363
|
-
const realRoot = await checkRealTarget("Grep",
|
|
15975
|
+
const realRoot = await checkRealTarget("Grep", path12.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
15364
15976
|
if (!realRoot.ok) return finish(errorResult4(realRoot.reason));
|
|
15365
15977
|
try {
|
|
15366
15978
|
new RegExp(args.pattern, args.case_insensitive ? "i" : "");
|
|
@@ -15445,7 +16057,7 @@ var grepTool = {
|
|
|
15445
16057
|
|
|
15446
16058
|
// ../kernel/src/tools/search/list-tool.ts
|
|
15447
16059
|
import { z as z20 } from "zod";
|
|
15448
|
-
import
|
|
16060
|
+
import path13 from "node:path";
|
|
15449
16061
|
var LIST_MAX_ENTRIES = 500;
|
|
15450
16062
|
var ListArgsSchema = z20.object({
|
|
15451
16063
|
path: z20.string().min(1),
|
|
@@ -15475,7 +16087,7 @@ var listTool = {
|
|
|
15475
16087
|
touchedPathsOf(args) {
|
|
15476
16088
|
const parsed = ListArgsSchema.safeParse(args);
|
|
15477
16089
|
if (!parsed.success || !isAbsolutePath(parsed.data.path)) return [];
|
|
15478
|
-
return [
|
|
16090
|
+
return [path13.resolve(parsed.data.path)];
|
|
15479
16091
|
},
|
|
15480
16092
|
async execute(args, ctx) {
|
|
15481
16093
|
if (ctx.signal.aborted) return errorResult5("aborted");
|
|
@@ -15489,7 +16101,7 @@ var listTool = {
|
|
|
15489
16101
|
return errorResult5(`path does not exist: ${root}`);
|
|
15490
16102
|
}
|
|
15491
16103
|
if (!rootStat.isDirectory()) return errorResult5(`path is not a directory: ${root}`);
|
|
15492
|
-
const realRoot = await checkRealTarget("List",
|
|
16104
|
+
const realRoot = await checkRealTarget("List", path13.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
15493
16105
|
if (!realRoot.ok) return errorResult5(realRoot.reason);
|
|
15494
16106
|
let ignoreMatchers = [];
|
|
15495
16107
|
try {
|
|
@@ -16020,7 +16632,7 @@ ${tail}`;
|
|
|
16020
16632
|
|
|
16021
16633
|
// ../kernel/src/tools/shell/output-file.ts
|
|
16022
16634
|
import { createWriteStream, mkdirSync } from "node:fs";
|
|
16023
|
-
import * as
|
|
16635
|
+
import * as path14 from "node:path";
|
|
16024
16636
|
var SHELL_OUTPUT_SPILL_THRESHOLD_CHARS = 5e4;
|
|
16025
16637
|
var SHELL_OUTPUT_DIR_NAME = "shell-output";
|
|
16026
16638
|
function sanitizeFileStem(stem) {
|
|
@@ -16029,7 +16641,7 @@ function sanitizeFileStem(stem) {
|
|
|
16029
16641
|
}
|
|
16030
16642
|
function shellOutputFilePath(sessionCwd, toolCallId) {
|
|
16031
16643
|
const stem = toolCallId !== void 0 && toolCallId.trim().length > 0 ? sanitizeFileStem(toolCallId) : `shell-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
16032
|
-
return
|
|
16644
|
+
return path14.join(sessionCwd, ".tansr", SHELL_OUTPUT_DIR_NAME, `${stem}.log`);
|
|
16033
16645
|
}
|
|
16034
16646
|
var ShellOutputSink = class {
|
|
16035
16647
|
filePath;
|
|
@@ -16065,7 +16677,7 @@ var ShellOutputSink = class {
|
|
|
16065
16677
|
forceOpen() {
|
|
16066
16678
|
if (this.#stream !== void 0 || this.#writeError !== void 0 || this.#closed) return;
|
|
16067
16679
|
try {
|
|
16068
|
-
mkdirSync(
|
|
16680
|
+
mkdirSync(path14.dirname(this.filePath), { recursive: true });
|
|
16069
16681
|
const stream = createWriteStream(this.filePath, { encoding: "utf8" });
|
|
16070
16682
|
stream.on("error", (err) => {
|
|
16071
16683
|
this.#writeError = err.message;
|
|
@@ -17556,120 +18168,23 @@ function scrubSecrets(text, secrets) {
|
|
|
17556
18168
|
}
|
|
17557
18169
|
|
|
17558
18170
|
// ../kernel/src/tools/web/http-search-provider.ts
|
|
17559
|
-
var WEBSEARCH_ENDPOINT_VAR = "TANSR_WEBSEARCH_ENDPOINT";
|
|
17560
|
-
var WEBSEARCH_API_KEY_VAR = "TANSR_WEBSEARCH_API_KEY";
|
|
17561
18171
|
var WEBSEARCH_RESPONSE_MAX_BYTES = 512 * 1024;
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
}
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
|
|
17578
|
-
);
|
|
17579
|
-
}
|
|
17580
|
-
function createHttpSearchProvider(options = {}) {
|
|
17581
|
-
const env = options.env ?? process.env;
|
|
17582
|
-
const endpoint = env[options.endpointVar ?? WEBSEARCH_ENDPOINT_VAR];
|
|
17583
|
-
if (endpoint === void 0 || endpoint.trim() === "") return null;
|
|
17584
|
-
const apiKey = env[options.apiKeyVar ?? WEBSEARCH_API_KEY_VAR];
|
|
17585
|
-
const fetchImpl = options.fetchImpl ?? defaultFetch2;
|
|
17586
|
-
const timeoutMs = options.timeoutMs ?? WEBSEARCH_PROVIDER_TIMEOUT_MS;
|
|
17587
|
-
const endpointLabel = safeEndpointLabel(endpoint);
|
|
17588
|
-
const scrub = (text) => scrubSecrets(text, [apiKey, endpoint]);
|
|
17589
|
-
return {
|
|
17590
|
-
name: options.name ?? "http",
|
|
17591
|
-
async search(query2, context) {
|
|
17592
|
-
const timeout = withTimeout(context.signal, timeoutMs);
|
|
17593
|
-
try {
|
|
17594
|
-
const headers = {
|
|
17595
|
-
accept: "application/json",
|
|
17596
|
-
"content-type": "application/json",
|
|
17597
|
-
"user-agent": "tansr-websearch"
|
|
17598
|
-
};
|
|
17599
|
-
if (apiKey !== void 0 && apiKey !== "") {
|
|
17600
|
-
headers["authorization"] = `Bearer ${apiKey}`;
|
|
17601
|
-
}
|
|
17602
|
-
const init = {
|
|
17603
|
-
method: "POST",
|
|
17604
|
-
signal: timeout.signal,
|
|
17605
|
-
redirect: "manual",
|
|
17606
|
-
headers,
|
|
17607
|
-
body: JSON.stringify({ query: query2.query, max_results: query2.maxResults })
|
|
17608
|
-
};
|
|
17609
|
-
let response;
|
|
17610
|
-
try {
|
|
17611
|
-
response = await fetchImpl(endpoint, init);
|
|
17612
|
-
} catch (err) {
|
|
17613
|
-
if (timeout.signal.aborted || isAbortError(err)) {
|
|
17614
|
-
throw new AbortError(timeout.signal.reason);
|
|
17615
|
-
}
|
|
17616
|
-
throw new Error(
|
|
17617
|
-
scrub(
|
|
17618
|
-
`search endpoint ${endpointLabel} is unreachable: ${err instanceof Error ? err.message : String(err)}`
|
|
17619
|
-
)
|
|
17620
|
-
);
|
|
17621
|
-
}
|
|
17622
|
-
const { bytes } = await readBodyWithLimit(
|
|
17623
|
-
response,
|
|
17624
|
-
WEBSEARCH_RESPONSE_MAX_BYTES,
|
|
17625
|
-
timeout.signal
|
|
17626
|
-
);
|
|
17627
|
-
const bodyText = new TextDecoder("utf-8").decode(bytes);
|
|
17628
|
-
if (response.status < 200 || response.status >= 300) {
|
|
17629
|
-
const preview = scrub(bodyText.slice(0, 200));
|
|
17630
|
-
throw new Error(
|
|
17631
|
-
`search endpoint ${endpointLabel} returned HTTP ${response.status}${preview !== "" ? `: ${preview}` : ""}`
|
|
17632
|
-
);
|
|
17633
|
-
}
|
|
17634
|
-
let payload;
|
|
17635
|
-
try {
|
|
17636
|
-
payload = JSON.parse(bodyText);
|
|
17637
|
-
} catch {
|
|
17638
|
-
throw new Error(`search endpoint ${endpointLabel} returned invalid JSON`);
|
|
17639
|
-
}
|
|
17640
|
-
const items = [];
|
|
17641
|
-
for (const item of pickItems(payload)) {
|
|
17642
|
-
if (typeof item.url !== "string" || item.url.trim() === "") continue;
|
|
17643
|
-
const snippet = typeof item.snippet === "string" ? item.snippet : typeof item.content === "string" ? item.content : "";
|
|
17644
|
-
items.push({
|
|
17645
|
-
title: scrub(typeof item.title === "string" ? item.title : ""),
|
|
17646
|
-
url: scrub(item.url),
|
|
17647
|
-
snippet: scrub(snippet)
|
|
17648
|
-
});
|
|
17649
|
-
}
|
|
17650
|
-
return items;
|
|
17651
|
-
} finally {
|
|
17652
|
-
timeout.release();
|
|
17653
|
-
}
|
|
17654
|
-
}
|
|
17655
|
-
};
|
|
17656
|
-
}
|
|
17657
|
-
|
|
17658
|
-
// ../kernel/src/tools/web/search-tool.ts
|
|
17659
|
-
import { z as z27 } from "zod";
|
|
17660
|
-
var WEBSEARCH_DEFAULT_RESULTS = 8;
|
|
17661
|
-
var WEBSEARCH_MAX_RESULTS = 20;
|
|
17662
|
-
var WEBSEARCH_TIMEOUT_MS = 3e4;
|
|
17663
|
-
var WEBSEARCH_MAX_TITLE_CHARS = 200;
|
|
17664
|
-
var WEBSEARCH_MAX_SNIPPET_CHARS = 500;
|
|
17665
|
-
var WebSearchArgsSchema = z27.object({
|
|
17666
|
-
query: z27.string().refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("The search query."),
|
|
17667
|
-
max_results: z27.number().int().min(1).max(WEBSEARCH_MAX_RESULTS).optional().describe(
|
|
17668
|
-
`Maximum number of results to return (default ${WEBSEARCH_DEFAULT_RESULTS}, max ${WEBSEARCH_MAX_RESULTS}).`
|
|
17669
|
-
)
|
|
17670
|
-
});
|
|
17671
|
-
function clip(text, maxChars) {
|
|
17672
|
-
return text.length <= maxChars ? text : `${text.slice(0, maxChars)}…`;
|
|
18172
|
+
|
|
18173
|
+
// ../kernel/src/tools/web/search-tool.ts
|
|
18174
|
+
import { z as z27 } from "zod";
|
|
18175
|
+
var WEBSEARCH_DEFAULT_RESULTS = 8;
|
|
18176
|
+
var WEBSEARCH_MAX_RESULTS = 20;
|
|
18177
|
+
var WEBSEARCH_TIMEOUT_MS = 3e4;
|
|
18178
|
+
var WEBSEARCH_MAX_TITLE_CHARS = 200;
|
|
18179
|
+
var WEBSEARCH_MAX_SNIPPET_CHARS = 500;
|
|
18180
|
+
var WebSearchArgsSchema = z27.object({
|
|
18181
|
+
query: z27.string().refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("The search query."),
|
|
18182
|
+
max_results: z27.number().int().min(1).max(WEBSEARCH_MAX_RESULTS).optional().describe(
|
|
18183
|
+
`Maximum number of results to return (default ${WEBSEARCH_DEFAULT_RESULTS}, max ${WEBSEARCH_MAX_RESULTS}).`
|
|
18184
|
+
)
|
|
18185
|
+
});
|
|
18186
|
+
function clip(text, maxChars) {
|
|
18187
|
+
return text.length <= maxChars ? text : `${text.slice(0, maxChars)}…`;
|
|
17673
18188
|
}
|
|
17674
18189
|
function sanitizeItems(raw, cap) {
|
|
17675
18190
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -18591,7 +19106,7 @@ function applyToolOverrides(filtered, overrides) {
|
|
|
18591
19106
|
}
|
|
18592
19107
|
|
|
18593
19108
|
// ../kernel/src/agents/subagent-memory-fence.ts
|
|
18594
|
-
import
|
|
19109
|
+
import path15 from "node:path";
|
|
18595
19110
|
function subagentMemoryFenceOf(ctx) {
|
|
18596
19111
|
if (ctx === void 0 || ctx.dirs.length === 0) return void 0;
|
|
18597
19112
|
return {
|
|
@@ -18606,7 +19121,7 @@ function writeTargetOf(args, cwd) {
|
|
|
18606
19121
|
if (args === null || typeof args !== "object") return null;
|
|
18607
19122
|
const raw = args.file_path;
|
|
18608
19123
|
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
18609
|
-
return
|
|
19124
|
+
return path15.isAbsolute(raw) ? path15.resolve(raw) : path15.resolve(cwd, raw);
|
|
18610
19125
|
}
|
|
18611
19126
|
function fencedSubagentWriteTool(tool, fence) {
|
|
18612
19127
|
return {
|
|
@@ -18914,10 +19429,10 @@ async function runSubagent(options) {
|
|
|
18914
19429
|
}
|
|
18915
19430
|
|
|
18916
19431
|
// ../kernel/src/tools/task/background.ts
|
|
18917
|
-
import * as
|
|
19432
|
+
import * as path16 from "node:path";
|
|
18918
19433
|
var TASK_AGENT_OUTPUT_DIR_NAME = "agent-output";
|
|
18919
19434
|
function taskAgentOutputFilePath(sessionCwd, agentId) {
|
|
18920
|
-
return
|
|
19435
|
+
return path16.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
|
|
18921
19436
|
}
|
|
18922
19437
|
function taskSettlementStopReason(reason, aborted) {
|
|
18923
19438
|
if (aborted) return "aborted";
|
|
@@ -20167,7 +20682,7 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
20167
20682
|
|
|
20168
20683
|
// ../kernel/src/tools/mcp/win32-spawn.ts
|
|
20169
20684
|
import fs2 from "node:fs";
|
|
20170
|
-
import
|
|
20685
|
+
import path17 from "node:path";
|
|
20171
20686
|
var CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
20172
20687
|
var SPAWNABLE_EXTS = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
20173
20688
|
function escapeCmdCommand(command) {
|
|
@@ -20206,7 +20721,7 @@ function defaultFileExists(filePath) {
|
|
|
20206
20721
|
}
|
|
20207
20722
|
}
|
|
20208
20723
|
function resolveCommandFile(command, env, cwd, fileExists) {
|
|
20209
|
-
const w =
|
|
20724
|
+
const w = path17.win32;
|
|
20210
20725
|
const exts = spawnableExts(envLookup(env, "PATHEXT"));
|
|
20211
20726
|
const bases = [];
|
|
20212
20727
|
if (command.includes("/") || command.includes("\\")) {
|
|
@@ -20240,7 +20755,7 @@ function wrapWithComSpec(target, args, env) {
|
|
|
20240
20755
|
function planStdioSpawn(command, args, options) {
|
|
20241
20756
|
const platform = options.platform ?? process.platform;
|
|
20242
20757
|
if (platform !== "win32") return { file: command, args };
|
|
20243
|
-
const ext =
|
|
20758
|
+
const ext = path17.win32.extname(command).toUpperCase();
|
|
20244
20759
|
if (ext === ".EXE" || ext === ".COM") return { file: command, args };
|
|
20245
20760
|
if (ext === ".CMD" || ext === ".BAT") return wrapWithComSpec(command, args, options.env);
|
|
20246
20761
|
if (ext !== "") return { file: command, args };
|
|
@@ -20248,7 +20763,7 @@ function planStdioSpawn(command, args, options) {
|
|
|
20248
20763
|
const fileExists = options.fileExists ?? defaultFileExists;
|
|
20249
20764
|
const resolved = resolveCommandFile(command, options.env, cwd, fileExists);
|
|
20250
20765
|
if (resolved === void 0) return { file: command, args };
|
|
20251
|
-
const resolvedExt =
|
|
20766
|
+
const resolvedExt = path17.win32.extname(resolved).toUpperCase();
|
|
20252
20767
|
if (resolvedExt === ".CMD" || resolvedExt === ".BAT") {
|
|
20253
20768
|
return wrapWithComSpec(resolved, args, options.env);
|
|
20254
20769
|
}
|
|
@@ -20378,11 +20893,11 @@ function buildMcpCatalogSegment(servers) {
|
|
|
20378
20893
|
if (text.length === 0) return null;
|
|
20379
20894
|
return { text, cacheable: true, label: MCP_CATALOG_LABEL };
|
|
20380
20895
|
}
|
|
20381
|
-
function
|
|
20896
|
+
function isRecord2(value) {
|
|
20382
20897
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20383
20898
|
}
|
|
20384
20899
|
function describeSchemaType(prop) {
|
|
20385
|
-
if (!
|
|
20900
|
+
if (!isRecord2(prop)) return "any";
|
|
20386
20901
|
if (Array.isArray(prop["enum"])) return "enum";
|
|
20387
20902
|
const type = prop["type"];
|
|
20388
20903
|
if (typeof type === "string") return type;
|
|
@@ -20393,7 +20908,7 @@ function describeSchemaType(prop) {
|
|
|
20393
20908
|
return "any";
|
|
20394
20909
|
}
|
|
20395
20910
|
function summarizeMcpParams(inputSchema) {
|
|
20396
|
-
const properties =
|
|
20911
|
+
const properties = isRecord2(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
|
|
20397
20912
|
if (properties === void 0 || Object.keys(properties).length === 0) {
|
|
20398
20913
|
return "(parameters unspecified)";
|
|
20399
20914
|
}
|
|
@@ -20549,14 +21064,14 @@ var SseParser = class {
|
|
|
20549
21064
|
var JSONRPC_VERSION = "2.0";
|
|
20550
21065
|
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
20551
21066
|
var JSONRPC_INTERNAL_ERROR = -32603;
|
|
20552
|
-
function
|
|
21067
|
+
function isRecord3(value) {
|
|
20553
21068
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20554
21069
|
}
|
|
20555
21070
|
function isValidId(value) {
|
|
20556
21071
|
return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
20557
21072
|
}
|
|
20558
21073
|
function classifyJsonRpcValue(value) {
|
|
20559
|
-
if (!
|
|
21074
|
+
if (!isRecord3(value)) return { ok: false, error: "invalid_message" };
|
|
20560
21075
|
if (value["jsonrpc"] !== JSONRPC_VERSION) return { ok: false, error: "invalid_message" };
|
|
20561
21076
|
const method = value["method"];
|
|
20562
21077
|
if (typeof method === "string" && method.length > 0) {
|
|
@@ -20569,7 +21084,7 @@ function classifyJsonRpcValue(value) {
|
|
|
20569
21084
|
if ("error" in value) {
|
|
20570
21085
|
const err = value["error"];
|
|
20571
21086
|
const idOk = value["id"] === null || isValidId(value["id"]);
|
|
20572
|
-
if (!idOk || !
|
|
21087
|
+
if (!idOk || !isRecord3(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
|
|
20573
21088
|
return { ok: false, error: "invalid_message" };
|
|
20574
21089
|
}
|
|
20575
21090
|
return { ok: true, kind: "error", message: value };
|
|
@@ -20612,14 +21127,14 @@ function buildErrorResponse(id, code, message, data) {
|
|
|
20612
21127
|
// ../kernel/src/tools/mcp/stream-connection.ts
|
|
20613
21128
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
20614
21129
|
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
20615
|
-
function
|
|
21130
|
+
function isRecord4(value) {
|
|
20616
21131
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20617
21132
|
}
|
|
20618
21133
|
function createDefaultServerRequestHandler(serverName, onEvent) {
|
|
20619
21134
|
return (method, params) => {
|
|
20620
21135
|
if (method === "ping") return Promise.resolve({ result: {} });
|
|
20621
21136
|
if (method === "elicitation/create") {
|
|
20622
|
-
const p =
|
|
21137
|
+
const p = isRecord4(params) ? params : {};
|
|
20623
21138
|
const mode = p["mode"] === "url" ? "url" : "form";
|
|
20624
21139
|
const message = typeof p["message"] === "string" ? p["message"].slice(0, 200) : void 0;
|
|
20625
21140
|
onEvent?.({
|
|
@@ -20868,7 +21383,7 @@ var StreamMcpConnection = class {
|
|
|
20868
21383
|
}
|
|
20869
21384
|
#handleServerNotification(notification) {
|
|
20870
21385
|
if (notification.method === "notifications/message") {
|
|
20871
|
-
const p =
|
|
21386
|
+
const p = isRecord4(notification.params) ? notification.params : {};
|
|
20872
21387
|
const level = typeof p["level"] === "string" ? p["level"] : "info";
|
|
20873
21388
|
const data = p["data"];
|
|
20874
21389
|
const message = typeof data === "string" ? data : data !== void 0 ? JSON.stringify(data) : "";
|
|
@@ -20882,7 +21397,7 @@ var StreamMcpConnection = class {
|
|
|
20882
21397
|
}
|
|
20883
21398
|
};
|
|
20884
21399
|
function validateInitializeResult(serverName, result) {
|
|
20885
|
-
if (!
|
|
21400
|
+
if (!isRecord4(result)) {
|
|
20886
21401
|
throw new McpError("handshake_failed", "MCP initialize returned a non-object result.", {
|
|
20887
21402
|
server: serverName
|
|
20888
21403
|
});
|
|
@@ -20907,8 +21422,8 @@ function validateInitializeResult(serverName, result) {
|
|
|
20907
21422
|
);
|
|
20908
21423
|
}
|
|
20909
21424
|
const rawInfo = result["serverInfo"];
|
|
20910
|
-
const serverInfo =
|
|
20911
|
-
const capabilities =
|
|
21425
|
+
const serverInfo = isRecord4(rawInfo) && typeof rawInfo["name"] === "string" && typeof rawInfo["version"] === "string" ? { name: rawInfo["name"], version: rawInfo["version"] } : void 0;
|
|
21426
|
+
const capabilities = isRecord4(result["capabilities"]) ? result["capabilities"] : {};
|
|
20912
21427
|
const rawInstructions = result["instructions"];
|
|
20913
21428
|
const instructions = typeof rawInstructions === "string" && rawInstructions.trim().length > 0 ? capMcpInstructions(rawInstructions).text : void 0;
|
|
20914
21429
|
return {
|
|
@@ -21112,7 +21627,7 @@ ${tail}`, {
|
|
|
21112
21627
|
}
|
|
21113
21628
|
|
|
21114
21629
|
// ../kernel/src/tools/mcp/http-transport.ts
|
|
21115
|
-
var
|
|
21630
|
+
var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
|
|
21116
21631
|
var SESSION_HEADER = "mcp-session-id";
|
|
21117
21632
|
var PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
21118
21633
|
var CANCEL_NOTIFY_TIMEOUT_MS = 3e3;
|
|
@@ -21139,7 +21654,7 @@ var HttpMcpConnection = class {
|
|
|
21139
21654
|
this.serverName = options.serverName;
|
|
21140
21655
|
this.#url = config.url;
|
|
21141
21656
|
this.#configHeaders = config.headers ?? {};
|
|
21142
|
-
this.#fetchImpl = options.fetchImpl ??
|
|
21657
|
+
this.#fetchImpl = options.fetchImpl ?? defaultFetch2;
|
|
21143
21658
|
this.#onEvent = options.onEvent;
|
|
21144
21659
|
this.#defaultRequestTimeoutMs = options.defaultRequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
21145
21660
|
this.#serverRequestHandler = options.onServerRequest ?? createDefaultServerRequestHandler(options.serverName, options.onEvent);
|
|
@@ -21504,7 +22019,7 @@ async function connectHttpMcpServer(config, options) {
|
|
|
21504
22019
|
}
|
|
21505
22020
|
|
|
21506
22021
|
// ../kernel/src/tools/mcp/bridge.ts
|
|
21507
|
-
import { createHash as
|
|
22022
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
21508
22023
|
import { z as z33 } from "zod";
|
|
21509
22024
|
var MCP_TOOL_NAME_PREFIX = "mcp__";
|
|
21510
22025
|
var MCP_REMOTE_TOOL_NAME_RE = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
@@ -21512,7 +22027,7 @@ var MAX_LIST_PAGES = 64;
|
|
|
21512
22027
|
function buildMcpToolName(serverName, toolName2) {
|
|
21513
22028
|
const full = `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName2}`;
|
|
21514
22029
|
if (full.length <= MCP_BRIDGED_TOOL_NAME_MAX_CHARS) return full;
|
|
21515
|
-
const hash =
|
|
22030
|
+
const hash = createHash7("sha256").update(full, "utf8").digest("hex").slice(0, MCP_NAME_FOLD_HASH_CHARS);
|
|
21516
22031
|
const prefix = `${MCP_TOOL_NAME_PREFIX}${serverName}__`;
|
|
21517
22032
|
const tailBudget = MCP_BRIDGED_TOOL_NAME_MAX_CHARS - prefix.length;
|
|
21518
22033
|
if (tailBudget >= MCP_NAME_FOLD_HASH_CHARS + 1) {
|
|
@@ -21522,7 +22037,7 @@ function buildMcpToolName(serverName, toolName2) {
|
|
|
21522
22037
|
const head = full.slice(0, MCP_BRIDGED_TOOL_NAME_MAX_CHARS - MCP_NAME_FOLD_HASH_CHARS - 1);
|
|
21523
22038
|
return `${head}_${hash}`;
|
|
21524
22039
|
}
|
|
21525
|
-
function
|
|
22040
|
+
function isRecord5(value) {
|
|
21526
22041
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21527
22042
|
}
|
|
21528
22043
|
async function discoverMcpTools(connection, opts = {}) {
|
|
@@ -21536,9 +22051,9 @@ async function discoverMcpTools(connection, opts = {}) {
|
|
|
21536
22051
|
cursor !== void 0 ? { cursor } : {},
|
|
21537
22052
|
opts
|
|
21538
22053
|
);
|
|
21539
|
-
if (!
|
|
22054
|
+
if (!isRecord5(result) || !Array.isArray(result["tools"])) break;
|
|
21540
22055
|
for (const raw of result["tools"]) {
|
|
21541
|
-
if (!
|
|
22056
|
+
if (!isRecord5(raw) || typeof raw["name"] !== "string") continue;
|
|
21542
22057
|
const name = raw["name"];
|
|
21543
22058
|
if (!MCP_REMOTE_TOOL_NAME_RE.test(name)) {
|
|
21544
22059
|
invalidNames.push(name.slice(0, 160));
|
|
@@ -21549,7 +22064,7 @@ async function discoverMcpTools(connection, opts = {}) {
|
|
|
21549
22064
|
tools.push({
|
|
21550
22065
|
name,
|
|
21551
22066
|
description: typeof raw["description"] === "string" ? raw["description"] : "",
|
|
21552
|
-
inputSchema:
|
|
22067
|
+
inputSchema: isRecord5(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
|
|
21553
22068
|
});
|
|
21554
22069
|
}
|
|
21555
22070
|
const next = result["nextCursor"];
|
|
@@ -21572,7 +22087,7 @@ function buildLooseArgsSchema(inputSchema) {
|
|
|
21572
22087
|
});
|
|
21573
22088
|
}
|
|
21574
22089
|
function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
21575
|
-
if (!
|
|
22090
|
+
if (!isRecord5(raw)) {
|
|
21576
22091
|
return {
|
|
21577
22092
|
content: [{ t: "text", text: "MCP server returned a malformed tools/call result." }],
|
|
21578
22093
|
isError: true,
|
|
@@ -21586,11 +22101,11 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
|
21586
22101
|
};
|
|
21587
22102
|
}
|
|
21588
22103
|
const isError = raw["isError"] === true;
|
|
21589
|
-
const structuredContent =
|
|
22104
|
+
const structuredContent = isRecord5(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
|
|
21590
22105
|
const blocks = Array.isArray(raw["content"]) ? raw["content"] : [];
|
|
21591
22106
|
const content = [];
|
|
21592
22107
|
for (const block of blocks) {
|
|
21593
|
-
if (!
|
|
22108
|
+
if (!isRecord5(block)) continue;
|
|
21594
22109
|
const type = block["type"];
|
|
21595
22110
|
if (type === "text" && typeof block["text"] === "string") {
|
|
21596
22111
|
content.push({ t: "text", text: block["text"] });
|
|
@@ -21612,7 +22127,7 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
|
21612
22127
|
content.push({ t: "text", text: `[resource link]${name} ${block["uri"]}${description}` });
|
|
21613
22128
|
continue;
|
|
21614
22129
|
}
|
|
21615
|
-
if (type === "resource" &&
|
|
22130
|
+
if (type === "resource" && isRecord5(block["resource"])) {
|
|
21616
22131
|
const resource = block["resource"];
|
|
21617
22132
|
const uri = typeof resource["uri"] === "string" ? resource["uri"] : "(unknown uri)";
|
|
21618
22133
|
if (typeof resource["text"] === "string") {
|
|
@@ -21893,7 +22408,7 @@ function extractMcpServersSection(layersDescending, addDiagnostic) {
|
|
|
21893
22408
|
}
|
|
21894
22409
|
|
|
21895
22410
|
// ../kernel/src/tools/mcp/catalog-store.ts
|
|
21896
|
-
import { createHash as
|
|
22411
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
21897
22412
|
var MCP_CATALOG_STORE_VERSION = 1;
|
|
21898
22413
|
function stableStringify2(value) {
|
|
21899
22414
|
if (value === null || typeof value !== "object") {
|
|
@@ -21907,10 +22422,10 @@ function stableStringify2(value) {
|
|
|
21907
22422
|
return `{${parts.join(",")}}`;
|
|
21908
22423
|
}
|
|
21909
22424
|
function computeMcpServerConfigFingerprint(config) {
|
|
21910
|
-
return
|
|
22425
|
+
return createHash8("sha256").update(stableStringify2(config), "utf8").digest("hex");
|
|
21911
22426
|
}
|
|
21912
22427
|
function computeMcpToolDefinitionHash(tool) {
|
|
21913
|
-
return
|
|
22428
|
+
return createHash8("sha256").update(
|
|
21914
22429
|
stableStringify2({
|
|
21915
22430
|
name: tool.name,
|
|
21916
22431
|
description: tool.description,
|
|
@@ -21988,14 +22503,14 @@ function resolveEnvRefs(record, baseEnv) {
|
|
|
21988
22503
|
}
|
|
21989
22504
|
return { resolved, missing };
|
|
21990
22505
|
}
|
|
21991
|
-
function
|
|
22506
|
+
function isRecord6(value) {
|
|
21992
22507
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21993
22508
|
}
|
|
21994
22509
|
function createElicitationAwareHandler(serverName, onElicitation, onEvent) {
|
|
21995
22510
|
const fallback = createDefaultServerRequestHandler(serverName, onEvent);
|
|
21996
22511
|
return async (method, params) => {
|
|
21997
22512
|
if (method !== "elicitation/create") return fallback(method, params);
|
|
21998
|
-
const p =
|
|
22513
|
+
const p = isRecord6(params) ? params : {};
|
|
21999
22514
|
const mode = p["mode"] === "url" ? "url" : "form";
|
|
22000
22515
|
try {
|
|
22001
22516
|
const response = await onElicitation({
|
|
@@ -22549,10 +23064,10 @@ async function initMcpLazyManager(toolset, servers, options = {}) {
|
|
|
22549
23064
|
}
|
|
22550
23065
|
|
|
22551
23066
|
// ../kernel/src/config/load.ts
|
|
22552
|
-
import
|
|
23067
|
+
import os3 from "node:os";
|
|
22553
23068
|
|
|
22554
23069
|
// ../kernel/src/hooks/config.ts
|
|
22555
|
-
import { createHash as
|
|
23070
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
22556
23071
|
|
|
22557
23072
|
// ../kernel/src/config/merge.ts
|
|
22558
23073
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -22582,12 +23097,12 @@ function deepMergeRawLayers(rawsAscending) {
|
|
|
22582
23097
|
}
|
|
22583
23098
|
return structuredClone(acc);
|
|
22584
23099
|
}
|
|
22585
|
-
function displayPath2(
|
|
22586
|
-
return
|
|
23100
|
+
function displayPath2(path23) {
|
|
23101
|
+
return path23.map(String).join(".");
|
|
22587
23102
|
}
|
|
22588
|
-
function getAtPath(root,
|
|
23103
|
+
function getAtPath(root, path23) {
|
|
22589
23104
|
let cur = root;
|
|
22590
|
-
for (const seg of
|
|
23105
|
+
for (const seg of path23) {
|
|
22591
23106
|
if (typeof seg === "number") {
|
|
22592
23107
|
if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return void 0;
|
|
22593
23108
|
cur = cur[seg];
|
|
@@ -22598,11 +23113,11 @@ function getAtPath(root, path20) {
|
|
|
22598
23113
|
}
|
|
22599
23114
|
return cur;
|
|
22600
23115
|
}
|
|
22601
|
-
function hasPath(root,
|
|
22602
|
-
if (
|
|
23116
|
+
function hasPath(root, path23) {
|
|
23117
|
+
if (path23.length === 0) return true;
|
|
22603
23118
|
let cur = root;
|
|
22604
|
-
for (let i = 0; i <
|
|
22605
|
-
const seg =
|
|
23119
|
+
for (let i = 0; i < path23.length; i++) {
|
|
23120
|
+
const seg = path23[i];
|
|
22606
23121
|
if (typeof seg === "number") {
|
|
22607
23122
|
if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return false;
|
|
22608
23123
|
cur = cur[seg];
|
|
@@ -22613,25 +23128,25 @@ function hasPath(root, path20) {
|
|
|
22613
23128
|
}
|
|
22614
23129
|
return true;
|
|
22615
23130
|
}
|
|
22616
|
-
function sanitizeDropPath(root,
|
|
23131
|
+
function sanitizeDropPath(root, path23) {
|
|
22617
23132
|
let cur = root;
|
|
22618
|
-
for (let i = 0; i <
|
|
22619
|
-
const seg =
|
|
23133
|
+
for (let i = 0; i < path23.length; i++) {
|
|
23134
|
+
const seg = path23[i];
|
|
22620
23135
|
const next = getAtPath(cur, [seg]);
|
|
22621
|
-
if (Array.isArray(next) && i <
|
|
22622
|
-
return
|
|
23136
|
+
if (Array.isArray(next) && i < path23.length - 1) {
|
|
23137
|
+
return path23.slice(0, i + 1);
|
|
22623
23138
|
}
|
|
22624
|
-
if (typeof
|
|
22625
|
-
return
|
|
23139
|
+
if (typeof path23[i + 1] === "number") {
|
|
23140
|
+
return path23.slice(0, i + 1);
|
|
22626
23141
|
}
|
|
22627
23142
|
cur = next;
|
|
22628
23143
|
}
|
|
22629
|
-
return
|
|
23144
|
+
return path23;
|
|
22630
23145
|
}
|
|
22631
|
-
function deletePath(root,
|
|
22632
|
-
if (
|
|
22633
|
-
const parent = getAtPath(root,
|
|
22634
|
-
const leaf =
|
|
23146
|
+
function deletePath(root, path23) {
|
|
23147
|
+
if (path23.length === 0) return;
|
|
23148
|
+
const parent = getAtPath(root, path23.slice(0, -1));
|
|
23149
|
+
const leaf = path23[path23.length - 1];
|
|
22635
23150
|
if (isPlainObject2(parent) && typeof leaf === "string") {
|
|
22636
23151
|
delete parent[leaf];
|
|
22637
23152
|
}
|
|
@@ -22721,16 +23236,16 @@ function canonicalJson(value) {
|
|
|
22721
23236
|
return JSON.stringify(value) ?? "null";
|
|
22722
23237
|
}
|
|
22723
23238
|
function configHash8(dedupeKey) {
|
|
22724
|
-
return
|
|
23239
|
+
return createHash9("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
|
|
22725
23240
|
}
|
|
22726
|
-
function parseExecutor(value, layer,
|
|
23241
|
+
function parseExecutor(value, layer, path23, file, addDiagnostic) {
|
|
22727
23242
|
const fileRef = file !== void 0 ? { file } : {};
|
|
22728
23243
|
const invalid = (params) => {
|
|
22729
23244
|
addDiagnostic({
|
|
22730
23245
|
severity: "error",
|
|
22731
23246
|
code: "hook_invalid_value",
|
|
22732
23247
|
layer,
|
|
22733
|
-
path:
|
|
23248
|
+
path: path23,
|
|
22734
23249
|
...fileRef,
|
|
22735
23250
|
params
|
|
22736
23251
|
});
|
|
@@ -22778,7 +23293,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
|
|
|
22778
23293
|
severity: "error",
|
|
22779
23294
|
code: "hook_url_invalid",
|
|
22780
23295
|
layer,
|
|
22781
|
-
path: `${
|
|
23296
|
+
path: `${path23}.url`,
|
|
22782
23297
|
...fileRef,
|
|
22783
23298
|
params: { reason: "unparsable" }
|
|
22784
23299
|
});
|
|
@@ -22789,7 +23304,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
|
|
|
22789
23304
|
severity: "error",
|
|
22790
23305
|
code: "hook_url_invalid",
|
|
22791
23306
|
layer,
|
|
22792
|
-
path: `${
|
|
23307
|
+
path: `${path23}.url`,
|
|
22793
23308
|
...fileRef,
|
|
22794
23309
|
params: { reason: "unsupported_protocol", protocol: parsed.protocol }
|
|
22795
23310
|
});
|
|
@@ -23380,7 +23895,7 @@ function migrateLayerRaw(raw) {
|
|
|
23380
23895
|
|
|
23381
23896
|
// ../kernel/src/config/paths.ts
|
|
23382
23897
|
import { promises as fsPromises } from "node:fs";
|
|
23383
|
-
import
|
|
23898
|
+
import path18 from "node:path";
|
|
23384
23899
|
var SETTINGS_FILE_NAME = "settings.json";
|
|
23385
23900
|
var LOCAL_SETTINGS_FILE_NAME = "settings.local.json";
|
|
23386
23901
|
var TANSR_DIR_NAME = ".tansr";
|
|
@@ -23407,27 +23922,27 @@ function normalizeForCompare(p, platform) {
|
|
|
23407
23922
|
function defaultManagedPaths(platform, env) {
|
|
23408
23923
|
if (platform === "win32") {
|
|
23409
23924
|
const programData = readTrimmedEnv2(env, "PROGRAMDATA") ?? "C:\\ProgramData";
|
|
23410
|
-
return [
|
|
23925
|
+
return [path18.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
|
|
23411
23926
|
}
|
|
23412
|
-
return [
|
|
23927
|
+
return [path18.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
|
|
23413
23928
|
}
|
|
23414
23929
|
async function discoverLayerFiles(options) {
|
|
23415
23930
|
const { fs: fs3, env, platform } = options;
|
|
23416
23931
|
const configDirOverride = readTrimmedEnv2(env, "TANSR_CONFIG_DIR");
|
|
23417
|
-
const userDir = configDirOverride ??
|
|
23418
|
-
const userFile =
|
|
23419
|
-
const homeKey = normalizeForCompare(
|
|
23932
|
+
const userDir = configDirOverride ?? path18.join(options.homedir, TANSR_DIR_NAME);
|
|
23933
|
+
const userFile = path18.join(userDir, SETTINGS_FILE_NAME);
|
|
23934
|
+
const homeKey = normalizeForCompare(path18.resolve(options.homedir), platform);
|
|
23420
23935
|
let projectRoot;
|
|
23421
|
-
const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(
|
|
23422
|
-
let cursor =
|
|
23936
|
+
const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path18.resolve(options.boundary), platform);
|
|
23937
|
+
let cursor = path18.resolve(options.cwd);
|
|
23423
23938
|
for (let depth = 0; depth < 256; depth++) {
|
|
23424
23939
|
const homeAnchorInvisible = normalizeForCompare(cursor, platform) === homeKey;
|
|
23425
|
-
if (!homeAnchorInvisible && await fs3.directoryExists(
|
|
23940
|
+
if (!homeAnchorInvisible && await fs3.directoryExists(path18.join(cursor, TANSR_DIR_NAME))) {
|
|
23426
23941
|
projectRoot = cursor;
|
|
23427
23942
|
break;
|
|
23428
23943
|
}
|
|
23429
23944
|
if (boundary !== void 0 && normalizeForCompare(cursor, platform) === boundary) break;
|
|
23430
|
-
const parent =
|
|
23945
|
+
const parent = path18.dirname(cursor);
|
|
23431
23946
|
if (parent === cursor) break;
|
|
23432
23947
|
cursor = parent;
|
|
23433
23948
|
}
|
|
@@ -23436,8 +23951,8 @@ async function discoverLayerFiles(options) {
|
|
|
23436
23951
|
managedCandidates,
|
|
23437
23952
|
...projectRoot !== void 0 ? {
|
|
23438
23953
|
projectRoot,
|
|
23439
|
-
projectSharedFile:
|
|
23440
|
-
projectLocalFile:
|
|
23954
|
+
projectSharedFile: path18.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
|
|
23955
|
+
projectLocalFile: path18.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
|
|
23441
23956
|
} : {},
|
|
23442
23957
|
userFile
|
|
23443
23958
|
};
|
|
@@ -23994,8 +24509,8 @@ function issueExpected(issue) {
|
|
|
23994
24509
|
return issue.code;
|
|
23995
24510
|
}
|
|
23996
24511
|
}
|
|
23997
|
-
function attributeLayer(layersDescending,
|
|
23998
|
-
return layersDescending.find((entry) => hasPath(entry.raw,
|
|
24512
|
+
function attributeLayer(layersDescending, path23) {
|
|
24513
|
+
return layersDescending.find((entry) => hasPath(entry.raw, path23));
|
|
23999
24514
|
}
|
|
24000
24515
|
function scanUnknownKeys(raw, parsed, pathSoFar, layersDescending, addDiagnostic) {
|
|
24001
24516
|
if (!isPlainObject2(raw) || !isPlainObject2(parsed)) return;
|
|
@@ -24168,7 +24683,7 @@ async function loadConfig(options = {}) {
|
|
|
24168
24683
|
const env = options.env ?? process.env;
|
|
24169
24684
|
const discovered = await discoverLayerFiles({
|
|
24170
24685
|
cwd: options.cwd ?? process.cwd(),
|
|
24171
|
-
homedir: options.homedir ??
|
|
24686
|
+
homedir: options.homedir ?? os3.homedir(),
|
|
24172
24687
|
env,
|
|
24173
24688
|
fs: fs3,
|
|
24174
24689
|
platform: options.platform ?? process.platform,
|
|
@@ -24248,9 +24763,9 @@ var OPEN_FENCE_RE = /^---[ \t]*\r?\n/;
|
|
|
24248
24763
|
var CLOSE_FENCE_RE = /^---[ \t]*$/;
|
|
24249
24764
|
function splitFrontmatterBlock(rawText) {
|
|
24250
24765
|
const text = stripBom2(rawText);
|
|
24251
|
-
const
|
|
24252
|
-
if (
|
|
24253
|
-
const rest = text.slice(
|
|
24766
|
+
const open4 = OPEN_FENCE_RE.exec(text);
|
|
24767
|
+
if (open4 === null) return null;
|
|
24768
|
+
const rest = text.slice(open4[0].length);
|
|
24254
24769
|
const lines = rest.split("\n");
|
|
24255
24770
|
for (let i = 0; i < lines.length; i++) {
|
|
24256
24771
|
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
@@ -25089,8 +25604,8 @@ var HookEngine = class {
|
|
|
25089
25604
|
|
|
25090
25605
|
// ../kernel/src/skills/discover.ts
|
|
25091
25606
|
import { promises as fsPromises2 } from "node:fs";
|
|
25092
|
-
import
|
|
25093
|
-
import
|
|
25607
|
+
import os4 from "node:os";
|
|
25608
|
+
import path19 from "node:path";
|
|
25094
25609
|
|
|
25095
25610
|
// ../kernel/src/skills/types.ts
|
|
25096
25611
|
var SKILLS_DIR_NAME = "skills";
|
|
@@ -25310,8 +25825,8 @@ async function scanFileSource(source, skillsDir, fs3, diagnostics) {
|
|
|
25310
25825
|
const seen = /* @__PURE__ */ new Set();
|
|
25311
25826
|
for (const dirName of [...subdirs].sort()) {
|
|
25312
25827
|
if (dirName.startsWith(".") || dirName.startsWith("_")) continue;
|
|
25313
|
-
const dir =
|
|
25314
|
-
const file =
|
|
25828
|
+
const dir = path19.join(skillsDir, dirName);
|
|
25829
|
+
const file = path19.join(dir, SKILL_FILE_NAME);
|
|
25315
25830
|
if (!SKILL_NAME_RE.test(dirName)) {
|
|
25316
25831
|
diagnostics.push({ severity: "warning", code: "name_invalid", source, skill: dirName, file });
|
|
25317
25832
|
continue;
|
|
@@ -25358,9 +25873,9 @@ async function discoverSkills(options = {}) {
|
|
|
25358
25873
|
const fs3 = options.fs ?? defaultSkillsFileSystem();
|
|
25359
25874
|
const env = options.env ?? process.env;
|
|
25360
25875
|
const platform = options.platform ?? process.platform;
|
|
25361
|
-
const homedir = options.homedir ??
|
|
25876
|
+
const homedir = options.homedir ?? os4.homedir();
|
|
25362
25877
|
const cwd = options.cwd ?? process.cwd();
|
|
25363
|
-
const explicitRoot = options.projectRoot === void 0 ? void 0 :
|
|
25878
|
+
const explicitRoot = options.projectRoot === void 0 ? void 0 : path19.resolve(options.projectRoot);
|
|
25364
25879
|
const discovered = await discoverLayerFiles({
|
|
25365
25880
|
cwd: explicitRoot ?? cwd,
|
|
25366
25881
|
homedir,
|
|
@@ -25371,13 +25886,13 @@ async function discoverSkills(options = {}) {
|
|
|
25371
25886
|
...explicitRoot !== void 0 ? { boundary: explicitRoot } : options.boundary !== void 0 ? { boundary: options.boundary } : {}
|
|
25372
25887
|
});
|
|
25373
25888
|
const builtinEntries = collectBuiltin(options.builtinSkills ?? [], diagnostics);
|
|
25374
|
-
const userSkillsDir =
|
|
25889
|
+
const userSkillsDir = path19.join(path19.dirname(discovered.userFile), SKILLS_DIR_NAME);
|
|
25375
25890
|
const userOutcome = await scanFileSource("user", userSkillsDir, fs3, diagnostics);
|
|
25376
25891
|
const projectRoot = explicitRoot ?? discovered.projectRoot;
|
|
25377
25892
|
let projectSkillsDir;
|
|
25378
25893
|
let projectOutcome = { status: "missing" };
|
|
25379
25894
|
if (projectRoot !== void 0) {
|
|
25380
|
-
projectSkillsDir =
|
|
25895
|
+
projectSkillsDir = path19.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
|
|
25381
25896
|
projectOutcome = await scanFileSource("project", projectSkillsDir, fs3, diagnostics);
|
|
25382
25897
|
}
|
|
25383
25898
|
const userEntries = userOutcome.status === "scanned" ? userOutcome.entries : [];
|
|
@@ -25451,7 +25966,7 @@ async function discoverSkills(options = {}) {
|
|
|
25451
25966
|
}
|
|
25452
25967
|
|
|
25453
25968
|
// ../kernel/src/skills/registry.ts
|
|
25454
|
-
import
|
|
25969
|
+
import path20 from "node:path";
|
|
25455
25970
|
function errCode2(err) {
|
|
25456
25971
|
if (err !== null && typeof err === "object" && "code" in err) {
|
|
25457
25972
|
const code = err.code;
|
|
@@ -25514,7 +26029,7 @@ var SkillRegistry = class {
|
|
|
25514
26029
|
/** 技能目录绝对路径(正文相对引用锚点;builtin 无) */
|
|
25515
26030
|
static baseDirOf(entry) {
|
|
25516
26031
|
if (entry.dir !== void 0) return entry.dir;
|
|
25517
|
-
if (entry.file !== void 0) return
|
|
26032
|
+
if (entry.file !== void 0) return path20.dirname(entry.file);
|
|
25518
26033
|
return void 0;
|
|
25519
26034
|
}
|
|
25520
26035
|
};
|
|
@@ -25718,9 +26233,9 @@ function defineSkill(options) {
|
|
|
25718
26233
|
];
|
|
25719
26234
|
return { name: options.name, content: lines.join("\n") };
|
|
25720
26235
|
}
|
|
25721
|
-
var NO_DISCOVERY_ROOT =
|
|
26236
|
+
var NO_DISCOVERY_ROOT = path21.resolve(path21.sep, ".tansr-sdk-no-discovery");
|
|
25722
26237
|
function noDiscoveryFs(inner) {
|
|
25723
|
-
const inVoid = (target) =>
|
|
26238
|
+
const inVoid = (target) => path21.resolve(target).startsWith(NO_DISCOVERY_ROOT);
|
|
25724
26239
|
const enoent = (target) => {
|
|
25725
26240
|
const err = new Error(`ENOENT: no such file or directory ${target}`);
|
|
25726
26241
|
err.code = "ENOENT";
|
|
@@ -25734,7 +26249,7 @@ function noDiscoveryFs(inner) {
|
|
|
25734
26249
|
}
|
|
25735
26250
|
async function assembleSdkSkills(options) {
|
|
25736
26251
|
const fs3 = noDiscoveryFs(options.fs ?? defaultSkillsFileSystem());
|
|
25737
|
-
const dirs = (options.dirs ?? []).map((dir) =>
|
|
26252
|
+
const dirs = (options.dirs ?? []).map((dir) => path21.resolve(dir));
|
|
25738
26253
|
for (const dir of dirs) {
|
|
25739
26254
|
if (!await fs3.directoryExists(dir)) {
|
|
25740
26255
|
throw new TansrSdkError(
|
|
@@ -27870,7 +28385,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
27870
28385
|
};
|
|
27871
28386
|
|
|
27872
28387
|
// ../providers/src/retry/attempt-observer.ts
|
|
27873
|
-
import { createHash as
|
|
28388
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
27874
28389
|
var PROVIDER_CALL_META_FIELD = "callMeta";
|
|
27875
28390
|
function providerCallMetaOf(options) {
|
|
27876
28391
|
if (options === void 0) return void 0;
|
|
@@ -27899,12 +28414,12 @@ function endpointKeyOf(baseUrl) {
|
|
|
27899
28414
|
let normalized;
|
|
27900
28415
|
try {
|
|
27901
28416
|
const url = new URL(baseUrl);
|
|
27902
|
-
const
|
|
27903
|
-
normalized = `${url.protocol}//${url.host}${
|
|
28417
|
+
const path23 = url.pathname.replace(/\/+$/, "");
|
|
28418
|
+
normalized = `${url.protocol}//${url.host}${path23}`.toLowerCase();
|
|
27904
28419
|
} catch {
|
|
27905
28420
|
normalized = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
|
|
27906
28421
|
}
|
|
27907
|
-
return
|
|
28422
|
+
return createHash10("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
|
|
27908
28423
|
}
|
|
27909
28424
|
function safeAttemptCall(fn) {
|
|
27910
28425
|
if (fn === void 0) return;
|
|
@@ -28041,14 +28556,14 @@ function sendableTwpReasoningOff(features) {
|
|
|
28041
28556
|
}
|
|
28042
28557
|
|
|
28043
28558
|
// ../providers/src/twp/signing.ts
|
|
28044
|
-
import { createHash as
|
|
28559
|
+
import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
|
|
28045
28560
|
var TWP_SIGNING_ALGORITHM = "TWP1-HMAC-SHA256";
|
|
28046
28561
|
var TWP_NONCE_LENGTH = 24;
|
|
28047
28562
|
function deriveTwpSigningKey(secretKey) {
|
|
28048
|
-
return
|
|
28563
|
+
return createHash11("sha256").update(secretKey, "utf8").digest();
|
|
28049
28564
|
}
|
|
28050
28565
|
function sha256Hex(data) {
|
|
28051
|
-
return
|
|
28566
|
+
return createHash11("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
|
|
28052
28567
|
}
|
|
28053
28568
|
function generateTwpNonce(random = randomBytes) {
|
|
28054
28569
|
return random(TWP_NONCE_LENGTH / 2).toString("hex");
|
|
@@ -28290,11 +28805,11 @@ function twpHttpStatusToIR(status, bodyText) {
|
|
|
28290
28805
|
if (status >= 500) return { kind: "server", message, recoverable: true };
|
|
28291
28806
|
return { kind: "invalid_request", message, recoverable: false };
|
|
28292
28807
|
}
|
|
28293
|
-
function
|
|
28808
|
+
function isRecord7(value) {
|
|
28294
28809
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28295
28810
|
}
|
|
28296
28811
|
function twpNonStreamToEvents(raw, fallbackModel) {
|
|
28297
|
-
if (!
|
|
28812
|
+
if (!isRecord7(raw)) {
|
|
28298
28813
|
return [
|
|
28299
28814
|
{
|
|
28300
28815
|
t: "error",
|
|
@@ -28312,7 +28827,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28312
28827
|
const blocks = Array.isArray(raw["blocks"]) ? raw["blocks"] : [];
|
|
28313
28828
|
let index = 0;
|
|
28314
28829
|
for (const rawBlock of blocks) {
|
|
28315
|
-
if (!
|
|
28830
|
+
if (!isRecord7(rawBlock)) continue;
|
|
28316
28831
|
if (rawBlock["t"] === "text" && typeof rawBlock["v"] === "string") {
|
|
28317
28832
|
out.push({ t: "block_start", index, block: { t: "text", text: rawBlock["v"] } });
|
|
28318
28833
|
out.push({ t: "block_stop", index });
|
|
@@ -28332,7 +28847,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28332
28847
|
index += 1;
|
|
28333
28848
|
}
|
|
28334
28849
|
}
|
|
28335
|
-
const usage =
|
|
28850
|
+
const usage = isRecord7(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
|
|
28336
28851
|
out.push({
|
|
28337
28852
|
t: "message_stop",
|
|
28338
28853
|
stopReason: mapTwpStop(typeof raw["stop"] === "string" ? raw["stop"] : void 0),
|
|
@@ -28341,7 +28856,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28341
28856
|
return out;
|
|
28342
28857
|
}
|
|
28343
28858
|
function twpNonStreamUsageFrame(raw) {
|
|
28344
|
-
if (!
|
|
28859
|
+
if (!isRecord7(raw) || !isRecord7(raw["usage"])) return null;
|
|
28345
28860
|
return parseTwpUsage(raw["usage"]);
|
|
28346
28861
|
}
|
|
28347
28862
|
|
|
@@ -29023,9 +29538,9 @@ var MissingApiKeyError = class extends ProviderRegistryError {
|
|
|
29023
29538
|
var CyclicFallbackError = class extends ProviderRegistryError {
|
|
29024
29539
|
/** 成环路径,末项为再次出现的别名,如 ['main','fast','main'] */
|
|
29025
29540
|
path;
|
|
29026
|
-
constructor(
|
|
29027
|
-
super("cyclic_fallback", `Cyclic fallback chain: ${
|
|
29028
|
-
this.path =
|
|
29541
|
+
constructor(path23) {
|
|
29542
|
+
super("cyclic_fallback", `Cyclic fallback chain: ${path23.join(" -> ")}`);
|
|
29543
|
+
this.path = path23;
|
|
29029
29544
|
}
|
|
29030
29545
|
};
|
|
29031
29546
|
|
|
@@ -29508,9 +30023,9 @@ function pushUnique(out, seen, resolved) {
|
|
|
29508
30023
|
seen.add(dedupeKey);
|
|
29509
30024
|
out.push(resolved);
|
|
29510
30025
|
}
|
|
29511
|
-
function expandInto(config, alias,
|
|
29512
|
-
if (
|
|
29513
|
-
const nextPath = [...
|
|
30026
|
+
function expandInto(config, alias, path23, out, seen) {
|
|
30027
|
+
if (path23.includes(alias)) throw new CyclicFallbackError([...path23, alias]);
|
|
30028
|
+
const nextPath = [...path23, alias];
|
|
29514
30029
|
pushUnique(out, seen, resolveAliasOrRef(config, alias));
|
|
29515
30030
|
for (const entry of config.fallbacks[alias] ?? []) {
|
|
29516
30031
|
if (entry.includes("/")) {
|
|
@@ -30337,14 +30852,21 @@ var AppQuotaSchema = z41.object({
|
|
|
30337
30852
|
import { z as z42 } from "zod";
|
|
30338
30853
|
var IMAGEGEN_TOOL_MAX_N = 4;
|
|
30339
30854
|
var ImageGenArgsSchema = z42.object({
|
|
30340
|
-
|
|
30341
|
-
|
|
30855
|
+
// S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
|
|
30856
|
+
// 第一生效(与 bundle platformModels.imageGen 首行恒同);点名限集内(工具描述自列)。
|
|
30857
|
+
model: z42.string().min(1).optional().describe(
|
|
30858
|
+
"Image model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model) when the user did not ask for a specific model. Never omit or swap this field as a workaround when the user named a model that is not in the set — tell the user and let them choose first."
|
|
30342
30859
|
),
|
|
30343
30860
|
prompt: z42.string().refine((s) => s.trim().length > 0, { message: "prompt must be a non-empty string" }).describe("Positive prompt describing the desired image content, style and composition."),
|
|
30344
30861
|
negativePrompt: z42.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the image."),
|
|
30345
30862
|
size: z42.string().regex(/^\d{2,5}\*\d{2,5}$/, { message: "size must look like '1024*1024' (width*height)" }).optional().describe("Output resolution as 'width*height' (for example '1024*1024'; defaults to the model's default)."),
|
|
30346
30863
|
n: z42.number().int().min(1).max(IMAGEGEN_TOOL_MAX_N).optional().describe(`Number of images to generate (default 1, max ${IMAGEGEN_TOOL_MAX_N}; each image is billed).`),
|
|
30347
|
-
seed: z42.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream).")
|
|
30864
|
+
seed: z42.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream)."),
|
|
30865
|
+
// 媒体二批富输入位(2026-09-01;网关 TwpImagegenRequestSchema 同帽 1..14):
|
|
30866
|
+
// 受理域随模型(工具描述逐模型注明;不受理的模型在场即 bad_request)。
|
|
30867
|
+
imageUrls: z42.array(z42.string().min(1)).min(1).max(14).optional().describe(
|
|
30868
|
+
"Input images for image editing or reference-conditioned generation (public http(s) URLs or data:image/*;base64 URIs). Only pass this for models whose bracketed note in this description lists imageUrls support; other models reject it."
|
|
30869
|
+
)
|
|
30348
30870
|
});
|
|
30349
30871
|
function errorResult13(text, model, errorCode2) {
|
|
30350
30872
|
const data = { model, images: [], imageCount: 0, ...errorCode2 !== void 0 ? { errorCode: errorCode2 } : {} };
|
|
@@ -30358,7 +30880,7 @@ function hintOf(code) {
|
|
|
30358
30880
|
return " Image generation is not configured on this platform yet; tell the user instead of retrying.";
|
|
30359
30881
|
}
|
|
30360
30882
|
if (code === "model_not_authorized") {
|
|
30361
|
-
return
|
|
30883
|
+
return ` The requested model is not in the authorized image model set of this app. Do not silently retry with another model or without "model" — tell the user, show the authorized image models from this tool's description, and let the user choose.`;
|
|
30362
30884
|
}
|
|
30363
30885
|
if (code === "imagegen_quota_exceeded") {
|
|
30364
30886
|
return " The daily image quota for this account is exhausted; retry after the daily reset (UTC+8).";
|
|
@@ -30368,28 +30890,59 @@ function hintOf(code) {
|
|
|
30368
30890
|
}
|
|
30369
30891
|
return "";
|
|
30370
30892
|
}
|
|
30893
|
+
function imageModelNote(m) {
|
|
30894
|
+
const label = m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model;
|
|
30895
|
+
const c = m.constraints;
|
|
30896
|
+
if (c === void 0) return label;
|
|
30897
|
+
const notes = [];
|
|
30898
|
+
if (!c.promptRequired) {
|
|
30899
|
+
notes.push("task-action model, NOT usable via this tool — pick another model");
|
|
30900
|
+
}
|
|
30901
|
+
if (!c.size) notes.push("fixed output size — do not pass size");
|
|
30902
|
+
if (c.imageInput !== null) {
|
|
30903
|
+
const forms = c.imageInput.urlOk && c.imageInput.b64Ok ? "URL or base64 data URI" : c.imageInput.urlOk ? "URL only" : "base64 data URI only";
|
|
30904
|
+
const count = c.imageInput.min > 0 ? `requires ${c.imageInput.min === c.imageInput.max ? String(c.imageInput.min) : `${c.imageInput.min}-${c.imageInput.max}`} input image(s)` : `accepts up to ${c.imageInput.max} reference image(s)`;
|
|
30905
|
+
notes.push(`${count} via imageUrls (${forms})`);
|
|
30906
|
+
}
|
|
30907
|
+
if (!c.seed) notes.push("no seed");
|
|
30908
|
+
if (!c.negativePrompt) notes.push("no negativePrompt");
|
|
30909
|
+
if (c.maxImages === 1) notes.push("single image per call (n=1)");
|
|
30910
|
+
return notes.length > 0 ? `${label} [${notes.join("; ")}]` : label;
|
|
30911
|
+
}
|
|
30912
|
+
function authorizedModelsNote(models) {
|
|
30913
|
+
if (models === void 0) {
|
|
30914
|
+
return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
|
|
30915
|
+
}
|
|
30916
|
+
if (models.length === 0) {
|
|
30917
|
+
return " The platform has no image model configured for this app yet — tell the user instead of retrying or guessing model names.";
|
|
30918
|
+
}
|
|
30919
|
+
const listed = models.map((m) => `- ${imageModelNote(m)}`).join("\n");
|
|
30920
|
+
return ' Authorized image models — the first is the default when "model" is omitted; bracketed notes are hard per-model constraints enforced by the platform (violating them fails the call):\n' + listed + '\nIf the user names a model outside this list (including a video model asked to make images), do not silently omit "model" or substitute another model — tell the user, show this list, and let them choose.';
|
|
30921
|
+
}
|
|
30371
30922
|
function createImageGenTool(options) {
|
|
30372
30923
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30373
30924
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30374
30925
|
const token = options.token;
|
|
30375
30926
|
return {
|
|
30376
30927
|
name: "ImageGen",
|
|
30377
|
-
description: "Generates images from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the image provider and bills the app per generated image). Returns image URLs that stay valid for roughly 24 hours — surface them to the user promptly. Requires the app to have the imageGen platform capability enabled.",
|
|
30378
|
-
shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model
|
|
30928
|
+
description: "Generates images from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the image provider and bills the app per generated image). Returns image URLs that stay valid for roughly 24 hours — surface them to the user promptly. Requires the app to have the imageGen platform capability enabled." + authorizedModelsNote(options.models),
|
|
30929
|
+
shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model?, prompt, negativePrompt?, size?, n?, seed?, imageUrls?.",
|
|
30379
30930
|
inputSchema: ImageGenArgsSchema,
|
|
30380
30931
|
isReadOnly: false,
|
|
30381
30932
|
isConcurrencySafe: false,
|
|
30382
30933
|
async execute(args, ctx) {
|
|
30934
|
+
const modelRef = args.model ?? "(default)";
|
|
30383
30935
|
if (ctx.signal.aborted) {
|
|
30384
|
-
return errorResult13("Tool execution was aborted.",
|
|
30936
|
+
return errorResult13("Tool execution was aborted.", modelRef);
|
|
30385
30937
|
}
|
|
30386
30938
|
const payload = {
|
|
30387
|
-
model: args.model,
|
|
30939
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
30388
30940
|
prompt: args.prompt,
|
|
30389
30941
|
...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
|
|
30390
30942
|
...args.size !== void 0 ? { size: args.size } : {},
|
|
30391
30943
|
...args.n !== void 0 ? { n: args.n } : {},
|
|
30392
|
-
...args.seed !== void 0 ? { seed: args.seed } : {}
|
|
30944
|
+
...args.seed !== void 0 ? { seed: args.seed } : {},
|
|
30945
|
+
...args.imageUrls !== void 0 ? { imageUrls: args.imageUrls } : {}
|
|
30393
30946
|
};
|
|
30394
30947
|
let response;
|
|
30395
30948
|
try {
|
|
@@ -30404,9 +30957,9 @@ function createImageGenTool(options) {
|
|
|
30404
30957
|
signal: ctx.signal
|
|
30405
30958
|
});
|
|
30406
30959
|
} catch (err) {
|
|
30407
|
-
if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.",
|
|
30960
|
+
if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", modelRef);
|
|
30408
30961
|
const message = err instanceof Error ? err.message : String(err);
|
|
30409
|
-
return errorResult13(`Image generation request failed to reach the platform: ${message}`,
|
|
30962
|
+
return errorResult13(`Image generation request failed to reach the platform: ${message}`, modelRef);
|
|
30410
30963
|
}
|
|
30411
30964
|
let raw = null;
|
|
30412
30965
|
try {
|
|
@@ -30418,17 +30971,18 @@ function createImageGenTool(options) {
|
|
|
30418
30971
|
const envelope = raw ?? {};
|
|
30419
30972
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30420
30973
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30421
|
-
return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`,
|
|
30974
|
+
return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`, modelRef, code);
|
|
30422
30975
|
}
|
|
30423
30976
|
const body = raw ?? {};
|
|
30424
30977
|
const images = Array.isArray(body.images) ? body.images.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
|
|
30425
30978
|
const imageCount = typeof body.imageCount === "number" ? body.imageCount : images.length;
|
|
30426
30979
|
if (images.length === 0) {
|
|
30427
|
-
return errorResult13("Image generation returned no image URL (unexpected platform response).",
|
|
30980
|
+
return errorResult13("Image generation returned no image URL (unexpected platform response).", modelRef);
|
|
30428
30981
|
}
|
|
30429
|
-
const
|
|
30982
|
+
const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
|
|
30983
|
+
const data = { model: resolvedModel, images, imageCount };
|
|
30430
30984
|
const lines = [
|
|
30431
|
-
`Generated ${imageCount} image(s) with model ${
|
|
30985
|
+
`Generated ${imageCount} image(s) with model ${resolvedModel} (billed per image).`,
|
|
30432
30986
|
"Image URLs (valid ~24h; surface or persist promptly):",
|
|
30433
30987
|
...images.map((img, i) => `${i + 1}. ${img.url}`)
|
|
30434
30988
|
];
|
|
@@ -30441,16 +30995,24 @@ function createImageGenTool(options) {
|
|
|
30441
30995
|
import { z as z43 } from "zod";
|
|
30442
30996
|
var VIDEOGEN_TOOL_MAX_DURATION = 30;
|
|
30443
30997
|
var VideoGenArgsSchema = z43.object({
|
|
30444
|
-
|
|
30445
|
-
|
|
30998
|
+
// S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
|
|
30999
|
+
// 第一生效(与 bundle platformModels.videoGen 首行恒同);点名限集内(工具描述自列)。
|
|
31000
|
+
model: z43.string().min(1).optional().describe(
|
|
31001
|
+
"Video model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model) when the user did not ask for a specific model. Never omit or swap this field as a workaround when the user named a model that is not in the set — tell the user and let them choose first."
|
|
30446
31002
|
),
|
|
30447
31003
|
prompt: z43.string().refine((s) => s.trim().length > 0, { message: "prompt must be a non-empty string" }).describe("Positive prompt describing the desired video content, motion, style and camera work."),
|
|
30448
31004
|
negativePrompt: z43.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the video."),
|
|
30449
31005
|
duration: z43.number().int().min(2).max(VIDEOGEN_TOOL_MAX_DURATION).optional().describe(
|
|
30450
|
-
|
|
31006
|
+
"Video duration in seconds (billing is per second — longer costs more). Models accept only the durations listed in their bracketed note; omit to use the model default."
|
|
30451
31007
|
),
|
|
30452
31008
|
ratio: z43.string().regex(/^(adaptive|\d{1,2}:\d{1,2})$/, { message: "ratio must look like '16:9' (or 'adaptive' where supported)" }).optional().describe("Aspect ratio such as '16:9', '9:16' or '1:1' (defaults to the model's default)."),
|
|
30453
|
-
seed: z43.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream).")
|
|
31009
|
+
seed: z43.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream)."),
|
|
31010
|
+
// 媒体二批富输入位(2026-09-01;网关 TwpVideogenRequestSchema 同帽 1..2):
|
|
31011
|
+
// i2v 首帧(seedance 支持首帧+尾帧两张);受理域随模型(描述逐模型注明,
|
|
31012
|
+
// i2v 专用行缺席即拒、纯文生行在场即拒)。
|
|
31013
|
+
imageUrls: z43.array(z43.string().min(1)).min(1).max(2).optional().describe(
|
|
31014
|
+
'First-frame image(s) for image-to-video generation (public http(s) URL or data:image/*;base64 URI). REQUIRED for models marked "image-to-video ONLY" in this description (generate or obtain an image first, e.g. via the ImageGen tool); rejected by text-to-video-only models.'
|
|
31015
|
+
)
|
|
30454
31016
|
});
|
|
30455
31017
|
function errorResult14(text, model, errorCode2) {
|
|
30456
31018
|
const data = {
|
|
@@ -30470,7 +31032,7 @@ function hintOf2(code) {
|
|
|
30470
31032
|
return " Video generation is not configured on this platform yet; tell the user instead of retrying.";
|
|
30471
31033
|
}
|
|
30472
31034
|
if (code === "model_not_authorized") {
|
|
30473
|
-
return
|
|
31035
|
+
return ` The requested model is not in the authorized video model set of this app. Do not silently retry with another model or without "model" — tell the user, show the authorized video models from this tool's description, and let the user choose.`;
|
|
30474
31036
|
}
|
|
30475
31037
|
if (code === "videogen_quota_exceeded") {
|
|
30476
31038
|
return " The daily video-seconds quota for this account is exhausted; retry after the daily reset (UTC+8).";
|
|
@@ -30478,30 +31040,68 @@ function hintOf2(code) {
|
|
|
30478
31040
|
if (code === "insufficient_balance") {
|
|
30479
31041
|
return " The app account balance is insufficient; the app developer needs to top up.";
|
|
30480
31042
|
}
|
|
31043
|
+
if (code === "upstream_error") {
|
|
31044
|
+
return " The video provider failed to execute the task. If imageUrls was passed, the provider may be unable to fetch that image host — retry with an image from a different image model or another public URL.";
|
|
31045
|
+
}
|
|
30481
31046
|
return "";
|
|
30482
31047
|
}
|
|
31048
|
+
function videoModelNote(m) {
|
|
31049
|
+
const label = m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model;
|
|
31050
|
+
const c = m.constraints;
|
|
31051
|
+
if (c === void 0) return label;
|
|
31052
|
+
const notes = [];
|
|
31053
|
+
const d = c.durations;
|
|
31054
|
+
notes.push(
|
|
31055
|
+
d.kind === "set" ? `duration ${d.values.join("|")}s ONLY (default ${d.defaultSec}s)` : `duration ${d.min}-${d.max}s (default ${d.defaultSec}s)`
|
|
31056
|
+
);
|
|
31057
|
+
if (c.imageInput === null) {
|
|
31058
|
+
notes.push("text-to-video only — rejects imageUrls");
|
|
31059
|
+
} else {
|
|
31060
|
+
const forms = c.imageInput.urlOk && c.imageInput.b64Ok ? "URL or base64 data URI" : c.imageInput.urlOk ? "URL only" : "base64 data URI only";
|
|
31061
|
+
notes.push(
|
|
31062
|
+
c.imageInput.required ? `image-to-video ONLY — first-frame image via imageUrls is REQUIRED (${forms})` : `optional first-frame image via imageUrls (${forms})`
|
|
31063
|
+
);
|
|
31064
|
+
}
|
|
31065
|
+
if (c.ratio === null) notes.push("no ratio");
|
|
31066
|
+
else if (c.ratio !== void 0) notes.push(`ratio ${c.ratio.join("|")}`);
|
|
31067
|
+
if (!c.seed) notes.push("no seed");
|
|
31068
|
+
if (!c.negativePrompt) notes.push("no negativePrompt");
|
|
31069
|
+
return `${label} [${notes.join("; ")}]`;
|
|
31070
|
+
}
|
|
31071
|
+
function authorizedModelsNote2(models) {
|
|
31072
|
+
if (models === void 0) {
|
|
31073
|
+
return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
|
|
31074
|
+
}
|
|
31075
|
+
if (models.length === 0) {
|
|
31076
|
+
return " The platform has no video model configured for this app yet — tell the user instead of retrying or guessing model names.";
|
|
31077
|
+
}
|
|
31078
|
+
const listed = models.map((m) => `- ${videoModelNote(m)}`).join("\n");
|
|
31079
|
+
return ' Authorized video models — the first is the default when "model" is omitted; bracketed notes are hard per-model constraints enforced by the platform (violating them fails the call):\n' + listed + '\nIf the user names a model outside this list (including an image model asked to make videos), do not silently omit "model" or substitute another model — tell the user, show this list, and let them choose.';
|
|
31080
|
+
}
|
|
30483
31081
|
function createVideoGenTool(options) {
|
|
30484
31082
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30485
31083
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30486
31084
|
const token = options.token;
|
|
30487
31085
|
return {
|
|
30488
31086
|
name: "VideoGen",
|
|
30489
|
-
description: "Generates a short video from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the video provider and bills the app per second of generated video). Generation is a long-running task (often minutes). Returns a video URL that stays valid for roughly 24 hours — surface it to the user promptly. Requires the app to have the videoGen platform capability enabled.",
|
|
30490
|
-
shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model
|
|
31087
|
+
description: "Generates a short video from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the video provider and bills the app per second of generated video). Generation is a long-running task (often minutes). Returns a video URL that stays valid for roughly 24 hours — surface it to the user promptly. Requires the app to have the videoGen platform capability enabled." + authorizedModelsNote2(options.models),
|
|
31088
|
+
shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model?, prompt, negativePrompt?, duration?, ratio?, seed?, imageUrls?.",
|
|
30491
31089
|
inputSchema: VideoGenArgsSchema,
|
|
30492
31090
|
isReadOnly: false,
|
|
30493
31091
|
isConcurrencySafe: false,
|
|
30494
31092
|
async execute(args, ctx) {
|
|
31093
|
+
const modelRef = args.model ?? "(default)";
|
|
30495
31094
|
if (ctx.signal.aborted) {
|
|
30496
|
-
return errorResult14("Tool execution was aborted.",
|
|
31095
|
+
return errorResult14("Tool execution was aborted.", modelRef);
|
|
30497
31096
|
}
|
|
30498
31097
|
const payload = {
|
|
30499
|
-
model: args.model,
|
|
31098
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
30500
31099
|
prompt: args.prompt,
|
|
30501
31100
|
...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
|
|
30502
31101
|
...args.duration !== void 0 ? { duration: args.duration } : {},
|
|
30503
31102
|
...args.ratio !== void 0 ? { ratio: args.ratio } : {},
|
|
30504
|
-
...args.seed !== void 0 ? { seed: args.seed } : {}
|
|
31103
|
+
...args.seed !== void 0 ? { seed: args.seed } : {},
|
|
31104
|
+
...args.imageUrls !== void 0 ? { imageUrls: args.imageUrls } : {}
|
|
30505
31105
|
};
|
|
30506
31106
|
let response;
|
|
30507
31107
|
try {
|
|
@@ -30516,9 +31116,9 @@ function createVideoGenTool(options) {
|
|
|
30516
31116
|
signal: ctx.signal
|
|
30517
31117
|
});
|
|
30518
31118
|
} catch (err) {
|
|
30519
|
-
if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.",
|
|
31119
|
+
if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", modelRef);
|
|
30520
31120
|
const message = err instanceof Error ? err.message : String(err);
|
|
30521
|
-
return errorResult14(`Video generation request failed to reach the platform: ${message}`,
|
|
31121
|
+
return errorResult14(`Video generation request failed to reach the platform: ${message}`, modelRef);
|
|
30522
31122
|
}
|
|
30523
31123
|
let raw = null;
|
|
30524
31124
|
try {
|
|
@@ -30530,18 +31130,19 @@ function createVideoGenTool(options) {
|
|
|
30530
31130
|
const envelope = raw ?? {};
|
|
30531
31131
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30532
31132
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30533
|
-
return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`,
|
|
31133
|
+
return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`, modelRef, code);
|
|
30534
31134
|
}
|
|
30535
31135
|
const body = raw ?? {};
|
|
30536
31136
|
const videos = Array.isArray(body.videos) ? body.videos.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
|
|
30537
31137
|
if (videos.length === 0) {
|
|
30538
|
-
return errorResult14("Video generation returned no video URL (unexpected platform response).",
|
|
31138
|
+
return errorResult14("Video generation returned no video URL (unexpected platform response).", modelRef);
|
|
30539
31139
|
}
|
|
30540
31140
|
const videoCount = typeof body.videoCount === "number" ? body.videoCount : videos.length;
|
|
30541
31141
|
const billedSeconds = typeof body.billedSeconds === "number" ? body.billedSeconds : 0;
|
|
30542
|
-
const
|
|
31142
|
+
const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
|
|
31143
|
+
const data = { model: resolvedModel, videos, videoCount, billedSeconds };
|
|
30543
31144
|
const lines = [
|
|
30544
|
-
`Generated ${videoCount} video(s) with model ${
|
|
31145
|
+
`Generated ${videoCount} video(s) with model ${resolvedModel} (${billedSeconds}s billed, per-second pricing).`,
|
|
30545
31146
|
"Video URLs (valid ~24h; surface or persist promptly):",
|
|
30546
31147
|
...videos.map((v, i) => `${i + 1}. ${v.url}`)
|
|
30547
31148
|
];
|
|
@@ -30550,24 +31151,8 @@ function createVideoGenTool(options) {
|
|
|
30550
31151
|
};
|
|
30551
31152
|
}
|
|
30552
31153
|
|
|
30553
|
-
// src/platform/websearch-
|
|
30554
|
-
|
|
30555
|
-
var WEBSEARCH_TOOL_MAX_RESULTS = 20;
|
|
30556
|
-
var WebSearchArgsSchema2 = z44.object({
|
|
30557
|
-
query: z44.string().max(1024).refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("Search query text (the platform never logs or stores it)."),
|
|
30558
|
-
max_results: z44.number().int().min(1).max(WEBSEARCH_TOOL_MAX_RESULTS).optional().describe(
|
|
30559
|
-
`Desired number of results (default 8, max ${WEBSEARCH_TOOL_MAX_RESULTS}; the provider may cap it lower). Billing is per call, not per result.`
|
|
30560
|
-
)
|
|
30561
|
-
});
|
|
30562
|
-
function errorResult15(text, query2, errorCode2) {
|
|
30563
|
-
const data = {
|
|
30564
|
-
query: query2,
|
|
30565
|
-
results: [],
|
|
30566
|
-
resultCount: 0,
|
|
30567
|
-
...errorCode2 !== void 0 ? { errorCode: errorCode2 } : {}
|
|
30568
|
-
};
|
|
30569
|
-
return { content: [{ t: "text", text }], isError: true, data };
|
|
30570
|
-
}
|
|
31154
|
+
// src/platform/websearch-provider.ts
|
|
31155
|
+
var PLATFORM_SEARCH_PROVIDER_NAME = "tansr-platform";
|
|
30571
31156
|
function hintOf3(code) {
|
|
30572
31157
|
if (code === "forbidden") {
|
|
30573
31158
|
return " The app platform capability webSearch is disabled; the app developer can enable it in console → app → capabilities.";
|
|
@@ -30589,25 +31174,13 @@ function hintOf3(code) {
|
|
|
30589
31174
|
}
|
|
30590
31175
|
return "";
|
|
30591
31176
|
}
|
|
30592
|
-
function
|
|
31177
|
+
function createPlatformSearchProvider(options) {
|
|
30593
31178
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30594
31179
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30595
31180
|
const token = options.token;
|
|
30596
31181
|
return {
|
|
30597
|
-
name:
|
|
30598
|
-
|
|
30599
|
-
shortDescription: "Search the web via the tansr platform (billed per call). Args: query, max_results?.",
|
|
30600
|
-
inputSchema: WebSearchArgsSchema2,
|
|
30601
|
-
isReadOnly: false,
|
|
30602
|
-
isConcurrencySafe: false,
|
|
30603
|
-
async execute(args, ctx) {
|
|
30604
|
-
if (ctx.signal.aborted) {
|
|
30605
|
-
return errorResult15("Tool execution was aborted.", args.query);
|
|
30606
|
-
}
|
|
30607
|
-
const payload = {
|
|
30608
|
-
query: args.query,
|
|
30609
|
-
...args.max_results !== void 0 ? { max_results: args.max_results } : {}
|
|
30610
|
-
};
|
|
31182
|
+
name: PLATFORM_SEARCH_PROVIDER_NAME,
|
|
31183
|
+
async search(query2, context) {
|
|
30611
31184
|
let response;
|
|
30612
31185
|
try {
|
|
30613
31186
|
response = await fetchImpl(`${base}/t1/websearch`, {
|
|
@@ -30617,13 +31190,15 @@ function createWebSearchTool2(options) {
|
|
|
30617
31190
|
accept: "application/json",
|
|
30618
31191
|
[HEADER_APP_TOKEN]: token
|
|
30619
31192
|
},
|
|
30620
|
-
|
|
30621
|
-
|
|
31193
|
+
// wire 体 snake_case(CLI 内核 HTTP 后端字节同构的既有特例);
|
|
31194
|
+
// maxResults 已被 kernel 工具层夹紧(缺省 8 / 上限 20,与网关帽同值)。
|
|
31195
|
+
body: JSON.stringify({ query: query2.query, max_results: query2.maxResults }),
|
|
31196
|
+
signal: context.signal
|
|
30622
31197
|
});
|
|
30623
31198
|
} catch (err) {
|
|
30624
|
-
if (
|
|
31199
|
+
if (context.signal.aborted) throw err;
|
|
30625
31200
|
const message = err instanceof Error ? err.message : String(err);
|
|
30626
|
-
|
|
31201
|
+
throw new Error(`web search request failed to reach the tansr platform: ${message}`);
|
|
30627
31202
|
}
|
|
30628
31203
|
let raw = null;
|
|
30629
31204
|
try {
|
|
@@ -30635,13 +31210,13 @@ function createWebSearchTool2(options) {
|
|
|
30635
31210
|
const envelope = raw ?? {};
|
|
30636
31211
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30637
31212
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30638
|
-
|
|
31213
|
+
throw new Error(`platform web search failed (${code}): ${message}.${hintOf3(code)}`);
|
|
30639
31214
|
}
|
|
30640
31215
|
const body = raw ?? {};
|
|
30641
31216
|
if (!Array.isArray(body.results)) {
|
|
30642
|
-
|
|
31217
|
+
throw new Error("platform web search returned an unexpected response (no results array).");
|
|
30643
31218
|
}
|
|
30644
|
-
|
|
31219
|
+
return body.results.map((item) => {
|
|
30645
31220
|
const row = item ?? {};
|
|
30646
31221
|
if (typeof row.title !== "string" || typeof row.url !== "string") return null;
|
|
30647
31222
|
return {
|
|
@@ -30650,19 +31225,6 @@ function createWebSearchTool2(options) {
|
|
|
30650
31225
|
snippet: typeof row.snippet === "string" ? row.snippet : ""
|
|
30651
31226
|
};
|
|
30652
31227
|
}).filter((item) => item !== null);
|
|
30653
|
-
const data = { query: args.query, results, resultCount: results.length };
|
|
30654
|
-
if (results.length === 0) {
|
|
30655
|
-
return { content: [{ t: "text", text: "Web search returned no results for this query (the call was still billed)." }], data };
|
|
30656
|
-
}
|
|
30657
|
-
const lines = [
|
|
30658
|
-
`Web search returned ${results.length} result(s) (billed per call):`,
|
|
30659
|
-
...results.map((item, i) => {
|
|
30660
|
-
const block = [`${i + 1}. ${item.title}`, ` ${item.url}`];
|
|
30661
|
-
if (item.snippet !== "") block.push(` ${item.snippet}`);
|
|
30662
|
-
return block.join("\n");
|
|
30663
|
-
})
|
|
30664
|
-
];
|
|
30665
|
-
return { content: [{ t: "text", text: lines.join("\n") }], data };
|
|
30666
31228
|
}
|
|
30667
31229
|
};
|
|
30668
31230
|
}
|
|
@@ -30682,7 +31244,7 @@ var BUILTIN_ORDER = [
|
|
|
30682
31244
|
"webSearch",
|
|
30683
31245
|
"http"
|
|
30684
31246
|
];
|
|
30685
|
-
var PLATFORM_NAMES = ["imageGen", "videoGen"
|
|
31247
|
+
var PLATFORM_NAMES = ["imageGen", "videoGen"];
|
|
30686
31248
|
function toToolDef(tool) {
|
|
30687
31249
|
const converted = zodToJsonSchema(tool.inputSchema);
|
|
30688
31250
|
const { $schema: _dropped, ...inputSchema } = converted;
|
|
@@ -30718,6 +31280,12 @@ function resolvePlatformSelection(selection, capabilities) {
|
|
|
30718
31280
|
if (selection === void 0) return [];
|
|
30719
31281
|
const requested = /* @__PURE__ */ new Set();
|
|
30720
31282
|
for (const name of selection) {
|
|
31283
|
+
if (name === "webSearch") {
|
|
31284
|
+
throw new TansrSdkError(
|
|
31285
|
+
"invalid_options",
|
|
31286
|
+
"Platform capability 'webSearch' moved: web search is now the builtin 'WebSearch' tool backed by the tansr platform channel (BYO endpoints are not supported in the SDK). Move 'webSearch' from tools.platform to tools.builtin (or omit tools to get it by default when the capability is enabled)."
|
|
31287
|
+
);
|
|
31288
|
+
}
|
|
30721
31289
|
if (!PLATFORM_NAMES.includes(name)) {
|
|
30722
31290
|
throw new TansrSdkError(
|
|
30723
31291
|
"invalid_options",
|
|
@@ -30785,7 +31353,7 @@ function buildBuiltinTool(name, materials) {
|
|
|
30785
31353
|
case "webFetch":
|
|
30786
31354
|
return createWebFetchTool();
|
|
30787
31355
|
case "webSearch":
|
|
30788
|
-
return createWebSearchTool({ provider:
|
|
31356
|
+
return createWebSearchTool({ provider: materials.searchProvider });
|
|
30789
31357
|
case "http":
|
|
30790
31358
|
return createHttpTool();
|
|
30791
31359
|
}
|
|
@@ -30793,9 +31361,37 @@ function buildBuiltinTool(name, materials) {
|
|
|
30793
31361
|
function buildSdkToolSet(options = {}) {
|
|
30794
31362
|
const capabilities = options.capabilities ?? DEFAULT_APP_CAPABILITIES;
|
|
30795
31363
|
const selection = options.tools ?? {};
|
|
30796
|
-
|
|
31364
|
+
let builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
|
|
30797
31365
|
const platformNames = resolvePlatformSelection(selection.platform, capabilities);
|
|
30798
31366
|
validateCustomSelection(selection.custom, capabilities);
|
|
31367
|
+
let webSearchProvider = null;
|
|
31368
|
+
if (builtinNames.includes("webSearch")) {
|
|
31369
|
+
const pc = options.platformContext;
|
|
31370
|
+
const explicit = selection.builtin !== void 0;
|
|
31371
|
+
if (pc === void 0) {
|
|
31372
|
+
if (explicit) {
|
|
31373
|
+
throw new TansrSdkError(
|
|
31374
|
+
"invalid_options",
|
|
31375
|
+
"tools.builtin ['webSearch'] requires the platform token tier: the SDK web search always rides the tansr platform channel (billed per call; local BYO endpoints are not supported). Create the session/query with { token, baseUrl }, or remove 'webSearch' from tools.builtin."
|
|
31376
|
+
);
|
|
31377
|
+
}
|
|
31378
|
+
builtinNames = builtinNames.filter((name) => name !== "webSearch");
|
|
31379
|
+
} else if (!capabilities.platform.webSearch) {
|
|
31380
|
+
if (explicit) {
|
|
31381
|
+
throw new TansrSdkError(
|
|
31382
|
+
"capability_disabled",
|
|
31383
|
+
`App platform capability 'webSearch' is disabled; ${CONSOLE_HINT}, or remove 'webSearch' from tools.builtin.`
|
|
31384
|
+
);
|
|
31385
|
+
}
|
|
31386
|
+
builtinNames = builtinNames.filter((name) => name !== "webSearch");
|
|
31387
|
+
} else {
|
|
31388
|
+
webSearchProvider = createPlatformSearchProvider({
|
|
31389
|
+
baseUrl: pc.baseUrl,
|
|
31390
|
+
token: pc.token,
|
|
31391
|
+
...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
|
|
31392
|
+
});
|
|
31393
|
+
}
|
|
31394
|
+
}
|
|
30799
31395
|
const platformTools = [];
|
|
30800
31396
|
for (const name of platformNames) {
|
|
30801
31397
|
const pc = options.platformContext;
|
|
@@ -30811,14 +31407,14 @@ function buildSdkToolSet(options = {}) {
|
|
|
30811
31407
|
...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
|
|
30812
31408
|
};
|
|
30813
31409
|
platformTools.push(
|
|
30814
|
-
name === "imageGen" ? createImageGenTool(materials
|
|
31410
|
+
name === "imageGen" ? createImageGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.imageGen } : {} }) : createVideoGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.videoGen } : {} })
|
|
30815
31411
|
);
|
|
30816
31412
|
}
|
|
30817
31413
|
const store = options.store ?? new TodoStore();
|
|
30818
31414
|
const channel = options.promptChannel ?? new UnavailableChannel();
|
|
30819
31415
|
const custom = selection.custom ?? [];
|
|
30820
31416
|
const tools = [
|
|
30821
|
-
...builtinNames.map((name) => buildBuiltinTool(name, { store, channel })),
|
|
31417
|
+
...builtinNames.map((name) => buildBuiltinTool(name, { store, channel, searchProvider: webSearchProvider })),
|
|
30822
31418
|
...platformTools,
|
|
30823
31419
|
...custom
|
|
30824
31420
|
];
|
|
@@ -31001,11 +31597,11 @@ async function assembleTooling(options) {
|
|
|
31001
31597
|
var TANSR_PROVIDER_ID = "tansr";
|
|
31002
31598
|
var APP_TOKEN_PLACEHOLDER_ENV = "TANSR_SDK_APP_TOKEN_TIER";
|
|
31003
31599
|
var APP_TOKEN_PLACEHOLDER_VALUE = "app-token-tier";
|
|
31004
|
-
function
|
|
31600
|
+
function isRecord8(value) {
|
|
31005
31601
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31006
31602
|
}
|
|
31007
31603
|
function parseModel(raw) {
|
|
31008
|
-
if (!
|
|
31604
|
+
if (!isRecord8(raw)) return null;
|
|
31009
31605
|
if (typeof raw.handle !== "string" || raw.handle === "" || typeof raw.modelId !== "string" || raw.modelId === "" || typeof raw.protocol !== "string") {
|
|
31010
31606
|
return null;
|
|
31011
31607
|
}
|
|
@@ -31013,25 +31609,103 @@ function parseModel(raw) {
|
|
|
31013
31609
|
handle: raw.handle,
|
|
31014
31610
|
modelId: raw.modelId,
|
|
31015
31611
|
displayName: typeof raw.displayName === "string" ? raw.displayName : raw.handle,
|
|
31612
|
+
// 模型呈现面二波(④)加法解析:缺键/坏形状 = null(旧 bundle 回落,恒不猜)
|
|
31613
|
+
manufacturer: typeof raw.manufacturer === "string" && raw.manufacturer !== "" ? raw.manufacturer : null,
|
|
31614
|
+
family: typeof raw.family === "string" && raw.family !== "" ? raw.family : null,
|
|
31016
31615
|
protocol: raw.protocol,
|
|
31017
|
-
capabilities:
|
|
31616
|
+
capabilities: isRecord8(raw.capabilities) ? raw.capabilities : {},
|
|
31018
31617
|
contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null
|
|
31019
31618
|
};
|
|
31020
31619
|
}
|
|
31620
|
+
function parseMediaImageInput(raw, flagKey) {
|
|
31621
|
+
if (raw === null) return null;
|
|
31622
|
+
if (!isRecord8(raw)) return false;
|
|
31623
|
+
const flag = raw[flagKey];
|
|
31624
|
+
if (flagKey === "min" ? typeof flag !== "number" : typeof flag !== "boolean") return false;
|
|
31625
|
+
if (typeof raw.max !== "number" || typeof raw.urlOk !== "boolean" || typeof raw.b64Ok !== "boolean") return false;
|
|
31626
|
+
return { flag, max: raw.max, urlOk: raw.urlOk, b64Ok: raw.b64Ok };
|
|
31627
|
+
}
|
|
31628
|
+
function parseImageConstraints(raw) {
|
|
31629
|
+
if (!isRecord8(raw)) return void 0;
|
|
31630
|
+
if (typeof raw.size !== "boolean" || typeof raw.seed !== "boolean" || typeof raw.negativePrompt !== "boolean" || typeof raw.maxImages !== "number" || typeof raw.promptRequired !== "boolean") {
|
|
31631
|
+
return void 0;
|
|
31632
|
+
}
|
|
31633
|
+
const input = parseMediaImageInput(raw.imageInput, "min");
|
|
31634
|
+
if (input === false) return void 0;
|
|
31635
|
+
return {
|
|
31636
|
+
size: raw.size,
|
|
31637
|
+
seed: raw.seed,
|
|
31638
|
+
negativePrompt: raw.negativePrompt,
|
|
31639
|
+
maxImages: raw.maxImages,
|
|
31640
|
+
imageInput: input === null ? null : { min: input.flag, max: input.max, urlOk: input.urlOk, b64Ok: input.b64Ok },
|
|
31641
|
+
promptRequired: raw.promptRequired,
|
|
31642
|
+
...raw.actionRequired === true ? { actionRequired: true } : {}
|
|
31643
|
+
};
|
|
31644
|
+
}
|
|
31645
|
+
function parseVideoConstraints(raw) {
|
|
31646
|
+
if (!isRecord8(raw)) return void 0;
|
|
31647
|
+
const d = raw.durations;
|
|
31648
|
+
let durations = null;
|
|
31649
|
+
if (isRecord8(d) && typeof d.defaultSec === "number") {
|
|
31650
|
+
if (d.kind === "set" && Array.isArray(d.values) && d.values.every((v) => typeof v === "number")) {
|
|
31651
|
+
durations = { kind: "set", values: [...d.values], defaultSec: d.defaultSec };
|
|
31652
|
+
} else if (d.kind === "range" && typeof d.min === "number" && typeof d.max === "number") {
|
|
31653
|
+
durations = { kind: "range", min: d.min, max: d.max, defaultSec: d.defaultSec };
|
|
31654
|
+
}
|
|
31655
|
+
}
|
|
31656
|
+
if (durations === null) return void 0;
|
|
31657
|
+
if (typeof raw.negativePrompt !== "boolean" || typeof raw.seed !== "boolean" || typeof raw.audioInput !== "boolean") {
|
|
31658
|
+
return void 0;
|
|
31659
|
+
}
|
|
31660
|
+
const input = parseMediaImageInput(raw.imageInput, "required");
|
|
31661
|
+
if (input === false) return void 0;
|
|
31662
|
+
let ratio;
|
|
31663
|
+
if (raw.ratio === void 0) ratio = void 0;
|
|
31664
|
+
else if (raw.ratio === null) ratio = null;
|
|
31665
|
+
else if (Array.isArray(raw.ratio) && raw.ratio.every((r) => typeof r === "string")) ratio = [...raw.ratio];
|
|
31666
|
+
else return void 0;
|
|
31667
|
+
return {
|
|
31668
|
+
durations,
|
|
31669
|
+
...ratio !== void 0 ? { ratio } : {},
|
|
31670
|
+
negativePrompt: raw.negativePrompt,
|
|
31671
|
+
seed: raw.seed,
|
|
31672
|
+
imageInput: input === null ? null : { required: input.flag, max: input.max, urlOk: input.urlOk, b64Ok: input.b64Ok },
|
|
31673
|
+
audioInput: raw.audioInput
|
|
31674
|
+
};
|
|
31675
|
+
}
|
|
31676
|
+
function parseMediaModels(raw, parseConstraints) {
|
|
31677
|
+
if (!Array.isArray(raw)) return [];
|
|
31678
|
+
const out = [];
|
|
31679
|
+
for (const item of raw) {
|
|
31680
|
+
if (!isRecord8(item) || typeof item.model !== "string" || item.model === "") continue;
|
|
31681
|
+
const constraints = item.constraints === void 0 ? void 0 : parseConstraints(item.constraints);
|
|
31682
|
+
out.push({
|
|
31683
|
+
model: item.model,
|
|
31684
|
+
displayName: typeof item.displayName === "string" && item.displayName !== "" ? item.displayName : item.model,
|
|
31685
|
+
...constraints !== void 0 ? { constraints } : {}
|
|
31686
|
+
});
|
|
31687
|
+
}
|
|
31688
|
+
return out;
|
|
31689
|
+
}
|
|
31021
31690
|
function parseAppBundle(raw) {
|
|
31022
|
-
if (!
|
|
31691
|
+
if (!isRecord8(raw)) return null;
|
|
31023
31692
|
const models = Array.isArray(raw.models) ? raw.models.map(parseModel).filter((m) => m !== null) : [];
|
|
31024
31693
|
const aliases = {};
|
|
31025
|
-
if (
|
|
31694
|
+
if (isRecord8(raw.aliases)) {
|
|
31026
31695
|
for (const [key2, value] of Object.entries(raw.aliases)) {
|
|
31027
31696
|
if (typeof value === "string" && value !== "") aliases[key2] = value;
|
|
31028
31697
|
}
|
|
31029
31698
|
}
|
|
31030
31699
|
const parsedCaps = AppCapabilitiesSchema.safeParse(raw.capabilities);
|
|
31700
|
+
const mediaRaw = isRecord8(raw.platformModels) ? raw.platformModels : {};
|
|
31031
31701
|
return {
|
|
31032
31702
|
models,
|
|
31033
31703
|
aliases,
|
|
31034
|
-
capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES
|
|
31704
|
+
capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES,
|
|
31705
|
+
platformModels: {
|
|
31706
|
+
imageGen: parseMediaModels(mediaRaw.imageGen, parseImageConstraints),
|
|
31707
|
+
videoGen: parseMediaModels(mediaRaw.videoGen, parseVideoConstraints)
|
|
31708
|
+
}
|
|
31035
31709
|
};
|
|
31036
31710
|
}
|
|
31037
31711
|
function sanitizeModelCapabilities(capabilities, contextWindow) {
|
|
@@ -31172,10 +31846,13 @@ async function assemblePlatformModel(options) {
|
|
|
31172
31846
|
const registry = new ProviderRegistry(appBundleRegistryConfig(bundle, base), {
|
|
31173
31847
|
// 占位哑值:满足装配期 readApiKey 非空校验;真实鉴权恒由 fetch 包装注入
|
|
31174
31848
|
env: { [APP_TOKEN_PLACEHOLDER_ENV]: APP_TOKEN_PLACEHOLDER_VALUE },
|
|
31175
|
-
fetchImpl: createAppTokenFetch(options.token, options.fetchImpl)
|
|
31849
|
+
fetchImpl: createAppTokenFetch(options.token, options.fetchImpl),
|
|
31850
|
+
// 拍板⑤:平台会话 id 供给经既有 TwpClientOptions.sessionId 缝出线——
|
|
31851
|
+
// registry 为会话私有(每次装配新建),setModel/Task 子代理同缝同值
|
|
31852
|
+
...options.sessionId !== void 0 ? { twp: { sessionId: options.sessionId } } : {}
|
|
31176
31853
|
});
|
|
31177
31854
|
const built = buildClientFromRegistry(registry, options.model ?? "main", options.onProviderSelected);
|
|
31178
|
-
return { ...built, registry, capabilities: bundle.capabilities };
|
|
31855
|
+
return { ...built, registry, capabilities: bundle.capabilities, platformModels: bundle.platformModels };
|
|
31179
31856
|
}
|
|
31180
31857
|
|
|
31181
31858
|
// src/task.ts
|
|
@@ -31264,7 +31941,9 @@ async function* consumeManaged(options) {
|
|
|
31264
31941
|
platformContext = {
|
|
31265
31942
|
baseUrl: options.baseUrl,
|
|
31266
31943
|
token: options.token,
|
|
31267
|
-
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
31944
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
31945
|
+
// S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
|
|
31946
|
+
platformModels: platform.platformModels
|
|
31268
31947
|
};
|
|
31269
31948
|
} else {
|
|
31270
31949
|
const managed = await assembleManagedModel({
|
|
@@ -31362,6 +32041,60 @@ async function* consumeHandle(handle) {
|
|
|
31362
32041
|
|
|
31363
32042
|
// src/session.ts
|
|
31364
32043
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
32044
|
+
|
|
32045
|
+
// src/sessions/pairing.ts
|
|
32046
|
+
var SYNTHETIC_TOOL_RESULT_TEXT = "Tool execution was interrupted before its result was persisted; this placeholder was synthesized while resuming the session.";
|
|
32047
|
+
function toolCallIds(message) {
|
|
32048
|
+
return message.blocks.flatMap((b) => b.t === "tool_call" ? [b.id] : []);
|
|
32049
|
+
}
|
|
32050
|
+
function toolResultIds(message) {
|
|
32051
|
+
return message.blocks.flatMap((b) => b.t === "tool_result" ? [b.callId] : []);
|
|
32052
|
+
}
|
|
32053
|
+
function repairHistoryPairing(messages) {
|
|
32054
|
+
const open4 = /* @__PURE__ */ new Set();
|
|
32055
|
+
let cleanEnd = 0;
|
|
32056
|
+
for (let i = 0; i < messages.length; i++) {
|
|
32057
|
+
const message = messages[i];
|
|
32058
|
+
if (message.role === "assistant") {
|
|
32059
|
+
for (const id of toolCallIds(message)) open4.add(id);
|
|
32060
|
+
} else {
|
|
32061
|
+
for (const callId of toolResultIds(message)) {
|
|
32062
|
+
if (!open4.has(callId)) {
|
|
32063
|
+
return {
|
|
32064
|
+
messages: messages.slice(0, cleanEnd).map((m) => structuredClone(m)),
|
|
32065
|
+
synthesizedResults: 0,
|
|
32066
|
+
droppedMessages: messages.length - cleanEnd
|
|
32067
|
+
};
|
|
32068
|
+
}
|
|
32069
|
+
open4.delete(callId);
|
|
32070
|
+
}
|
|
32071
|
+
}
|
|
32072
|
+
if (open4.size === 0) cleanEnd = i + 1;
|
|
32073
|
+
}
|
|
32074
|
+
if (open4.size === 0) {
|
|
32075
|
+
return {
|
|
32076
|
+
messages: messages.map((m) => structuredClone(m)),
|
|
32077
|
+
synthesizedResults: 0,
|
|
32078
|
+
droppedMessages: 0
|
|
32079
|
+
};
|
|
32080
|
+
}
|
|
32081
|
+
const synthetic = {
|
|
32082
|
+
role: "user",
|
|
32083
|
+
blocks: [...open4].map((callId) => ({
|
|
32084
|
+
t: "tool_result",
|
|
32085
|
+
callId,
|
|
32086
|
+
content: [{ t: "text", text: SYNTHETIC_TOOL_RESULT_TEXT }],
|
|
32087
|
+
isError: true
|
|
32088
|
+
}))
|
|
32089
|
+
};
|
|
32090
|
+
return {
|
|
32091
|
+
messages: [...messages.map((m) => structuredClone(m)), synthetic],
|
|
32092
|
+
synthesizedResults: open4.size,
|
|
32093
|
+
droppedMessages: 0
|
|
32094
|
+
};
|
|
32095
|
+
}
|
|
32096
|
+
|
|
32097
|
+
// src/session.ts
|
|
31365
32098
|
function createEventQueue() {
|
|
31366
32099
|
const buffered = [];
|
|
31367
32100
|
let ended = false;
|
|
@@ -31637,7 +32370,21 @@ var AgentSession = class {
|
|
|
31637
32370
|
};
|
|
31638
32371
|
async function createSession(options = {}) {
|
|
31639
32372
|
const cwd = options.cwd ?? process.cwd();
|
|
31640
|
-
|
|
32373
|
+
if (options.resume !== void 0) {
|
|
32374
|
+
if (options.initialMessages !== void 0) {
|
|
32375
|
+
throw new TansrSdkError(
|
|
32376
|
+
"invalid_options",
|
|
32377
|
+
'createSession: "resume" and "initialMessages" are mutually exclusive; "resume" loads the history from the store ("initialMessages" is the low-level re-feed escape hatch).'
|
|
32378
|
+
);
|
|
32379
|
+
}
|
|
32380
|
+
if (options.sessionId !== void 0 && options.sessionId !== options.resume.sessionId) {
|
|
32381
|
+
throw new TansrSdkError(
|
|
32382
|
+
"invalid_options",
|
|
32383
|
+
'createSession: "sessionId" conflicts with "resume.sessionId"; pass one of them (they must match).'
|
|
32384
|
+
);
|
|
32385
|
+
}
|
|
32386
|
+
}
|
|
32387
|
+
const sessionId = options.sessionId ?? options.resume?.sessionId ?? randomUUID11();
|
|
31641
32388
|
const injected = options.client !== void 0;
|
|
31642
32389
|
const tokenTier = options.token !== void 0;
|
|
31643
32390
|
if (injected && typeof options.model === "string") {
|
|
@@ -31653,6 +32400,18 @@ async function createSession(options = {}) {
|
|
|
31653
32400
|
);
|
|
31654
32401
|
}
|
|
31655
32402
|
validateTokenTierOptions("createSession", options);
|
|
32403
|
+
let initialMessages = options.initialMessages ?? [];
|
|
32404
|
+
if (options.resume !== void 0) {
|
|
32405
|
+
const record = await options.resume.store.get(options.resume.sessionId);
|
|
32406
|
+
if (record === null) {
|
|
32407
|
+
throw new TansrSdkError(
|
|
32408
|
+
"session_not_found",
|
|
32409
|
+
`createSession: session "${options.resume.sessionId}" was not found in the given store; list() the store or create a fresh session instead.`
|
|
32410
|
+
);
|
|
32411
|
+
}
|
|
32412
|
+
initialMessages = repairHistoryPairing(record.messages).messages;
|
|
32413
|
+
}
|
|
32414
|
+
const platformSessionId = tokenTier ? ulid() : void 0;
|
|
31656
32415
|
let sessionRef = null;
|
|
31657
32416
|
const emitBody = (body, source) => {
|
|
31658
32417
|
sessionRef?.pushBody(body, source);
|
|
@@ -31675,6 +32434,9 @@ async function createSession(options = {}) {
|
|
|
31675
32434
|
baseUrl: options.baseUrl,
|
|
31676
32435
|
...typeof options.model === "string" ? { model: options.model } : {},
|
|
31677
32436
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
32437
|
+
// 拍板⑤:本会话全部 exchange(含 setModel 热切换与 Task 子代理,同
|
|
32438
|
+
// registry 同缝)恒携同一枚平台会话 ULID——会话维归因/亲和自此在场
|
|
32439
|
+
...platformSessionId !== void 0 ? { sessionId: () => platformSessionId } : {},
|
|
31678
32440
|
onProviderSelected: (selection) => {
|
|
31679
32441
|
const body = providerSwitchedBody(selection);
|
|
31680
32442
|
if (body !== null) emitBody(body, "provider");
|
|
@@ -31691,7 +32453,9 @@ async function createSession(options = {}) {
|
|
|
31691
32453
|
platformContext = {
|
|
31692
32454
|
baseUrl: options.baseUrl,
|
|
31693
32455
|
token: options.token,
|
|
31694
|
-
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
32456
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
32457
|
+
// S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
|
|
32458
|
+
platformModels: assembled.platformModels
|
|
31695
32459
|
};
|
|
31696
32460
|
} else {
|
|
31697
32461
|
const assembled = await assembleManagedModel({
|
|
@@ -31747,6 +32511,26 @@ async function createSession(options = {}) {
|
|
|
31747
32511
|
}
|
|
31748
32512
|
);
|
|
31749
32513
|
}
|
|
32514
|
+
const store = options.store;
|
|
32515
|
+
if (store !== void 0) {
|
|
32516
|
+
await store.create({
|
|
32517
|
+
sessionId,
|
|
32518
|
+
...platformSessionId !== void 0 ? { platformSessionId } : {}
|
|
32519
|
+
});
|
|
32520
|
+
}
|
|
32521
|
+
const userCommit = options.onHistoryCommit;
|
|
32522
|
+
const onHistoryCommit = store === void 0 ? userCommit : async (history, meta) => {
|
|
32523
|
+
let storeError;
|
|
32524
|
+
try {
|
|
32525
|
+
await store.commit(sessionId, history, meta);
|
|
32526
|
+
} catch (err) {
|
|
32527
|
+
storeError = err;
|
|
32528
|
+
}
|
|
32529
|
+
await userCommit?.(history, meta);
|
|
32530
|
+
if (storeError !== void 0) {
|
|
32531
|
+
throw storeError instanceof Error ? storeError : new Error(String(storeError));
|
|
32532
|
+
}
|
|
32533
|
+
};
|
|
31750
32534
|
const session = new AgentSession({
|
|
31751
32535
|
binding,
|
|
31752
32536
|
executor: tooling.executor,
|
|
@@ -31757,9 +32541,9 @@ async function createSession(options = {}) {
|
|
|
31757
32541
|
maxTokens,
|
|
31758
32542
|
maxTurnsPerQuery: options.maxTurnsPerQuery ?? DEFAULT_MAX_TURNS,
|
|
31759
32543
|
compaction: options.compaction === false ? void 0 : options.compaction ?? {},
|
|
31760
|
-
initialMessages
|
|
32544
|
+
initialMessages,
|
|
31761
32545
|
registry,
|
|
31762
|
-
onHistoryCommit
|
|
32546
|
+
onHistoryCommit,
|
|
31763
32547
|
...tooling.onEnded !== void 0 ? { onEnded: tooling.onEnded } : {}
|
|
31764
32548
|
});
|
|
31765
32549
|
sessionRef = session;
|
|
@@ -31770,6 +32554,272 @@ async function createSession(options = {}) {
|
|
|
31770
32554
|
return session;
|
|
31771
32555
|
}
|
|
31772
32556
|
|
|
32557
|
+
// src/sessions/file-store.ts
|
|
32558
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
32559
|
+
import { mkdir as mkdir3, readFile as readFile5, readdir, rename as rename2, rm as rm3 } from "node:fs/promises";
|
|
32560
|
+
import path22 from "node:path";
|
|
32561
|
+
var MESSAGE_KINDS2 = /* @__PURE__ */ new Set([
|
|
32562
|
+
"user_prompt",
|
|
32563
|
+
"assistant_message",
|
|
32564
|
+
"tool_result"
|
|
32565
|
+
]);
|
|
32566
|
+
function journalKindOf(message) {
|
|
32567
|
+
if (message.role === "assistant") return "assistant_message";
|
|
32568
|
+
return message.blocks.some((b) => b.t === "tool_result") ? "tool_result" : "user_prompt";
|
|
32569
|
+
}
|
|
32570
|
+
var TITLE_MAX_CHARS = 64;
|
|
32571
|
+
function sessionTitleOf(messages) {
|
|
32572
|
+
for (const message of messages) {
|
|
32573
|
+
if (message.role !== "user") continue;
|
|
32574
|
+
const text = message.blocks.flatMap((b) => b.t === "text" ? [b.text] : []).join(" ").replace(/\s+/g, " ").trim();
|
|
32575
|
+
if (text === "") continue;
|
|
32576
|
+
return text.length > TITLE_MAX_CHARS ? `${text.slice(0, TITLE_MAX_CHARS)}…` : text;
|
|
32577
|
+
}
|
|
32578
|
+
return void 0;
|
|
32579
|
+
}
|
|
32580
|
+
async function readRawMeta(sessionDir) {
|
|
32581
|
+
try {
|
|
32582
|
+
const parsed = JSON.parse(await readFile5(metaFilePath(sessionDir), "utf8"));
|
|
32583
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
32584
|
+
return parsed;
|
|
32585
|
+
} catch {
|
|
32586
|
+
return null;
|
|
32587
|
+
}
|
|
32588
|
+
}
|
|
32589
|
+
function toRecordMeta(raw) {
|
|
32590
|
+
const createdAt = typeof raw["createdAt"] === "string" ? raw["createdAt"] : "";
|
|
32591
|
+
const updatedAt = typeof raw["updatedAt"] === "string" ? raw["updatedAt"] : createdAt;
|
|
32592
|
+
const record = {
|
|
32593
|
+
sessionId: typeof raw["sessionId"] === "string" ? raw["sessionId"] : "",
|
|
32594
|
+
createdAt,
|
|
32595
|
+
updatedAt
|
|
32596
|
+
};
|
|
32597
|
+
if (typeof raw["title"] === "string") record.title = raw["title"];
|
|
32598
|
+
if (typeof raw["platformSessionId"] === "string" && raw["platformSessionId"] !== "") {
|
|
32599
|
+
record.platformSessionId = raw["platformSessionId"];
|
|
32600
|
+
}
|
|
32601
|
+
const extra = raw["meta"];
|
|
32602
|
+
if (typeof extra === "object" && extra !== null && !Array.isArray(extra)) {
|
|
32603
|
+
record.meta = extra;
|
|
32604
|
+
}
|
|
32605
|
+
return record;
|
|
32606
|
+
}
|
|
32607
|
+
function corrupted(sessionId, detail, cause) {
|
|
32608
|
+
return new TansrSdkError(
|
|
32609
|
+
"session_store_corrupted",
|
|
32610
|
+
`Session store for "${sessionId}" is corrupted: ${detail}. Refusing to resume from damaged history (delete the session directory to start over).`,
|
|
32611
|
+
cause !== void 0 ? { cause } : void 0
|
|
32612
|
+
);
|
|
32613
|
+
}
|
|
32614
|
+
function createFileSessionStore(options) {
|
|
32615
|
+
const storageRoot = options.dir;
|
|
32616
|
+
const tails = /* @__PURE__ */ new Map();
|
|
32617
|
+
function enqueue(sessionId, op) {
|
|
32618
|
+
const tail = tails.get(sessionId) ?? Promise.resolve();
|
|
32619
|
+
const task = tail.then(op);
|
|
32620
|
+
tails.set(
|
|
32621
|
+
sessionId,
|
|
32622
|
+
task.then(
|
|
32623
|
+
() => void 0,
|
|
32624
|
+
() => void 0
|
|
32625
|
+
)
|
|
32626
|
+
);
|
|
32627
|
+
return task;
|
|
32628
|
+
}
|
|
32629
|
+
async function patchRawMeta(sessionDir, mutate) {
|
|
32630
|
+
const raw = await readRawMeta(sessionDir);
|
|
32631
|
+
if (raw === null) return null;
|
|
32632
|
+
if (mutate(raw)) {
|
|
32633
|
+
await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(raw, null, 2)}
|
|
32634
|
+
`);
|
|
32635
|
+
}
|
|
32636
|
+
return raw;
|
|
32637
|
+
}
|
|
32638
|
+
async function appendMessages(writer, sessionId, parentEventId, messages) {
|
|
32639
|
+
let parent = parentEventId;
|
|
32640
|
+
for (const message of messages) {
|
|
32641
|
+
const eventId = randomUUID12();
|
|
32642
|
+
await writer.append({
|
|
32643
|
+
eventId,
|
|
32644
|
+
sessionId,
|
|
32645
|
+
branchId: MAIN_BRANCH_ID,
|
|
32646
|
+
kind: journalKindOf(message),
|
|
32647
|
+
parentEventId: parent,
|
|
32648
|
+
payload: message,
|
|
32649
|
+
producer: "sdk",
|
|
32650
|
+
visibility: { ...Visibilities.normal }
|
|
32651
|
+
});
|
|
32652
|
+
parent = eventId;
|
|
32653
|
+
}
|
|
32654
|
+
}
|
|
32655
|
+
async function rotateVolume(sessionDir, sessionId, history) {
|
|
32656
|
+
const tempDir = path22.join(sessionDir, `.rotate-${process.pid}-${Date.now().toString(36)}`);
|
|
32657
|
+
await mkdir3(tempDir, { recursive: true });
|
|
32658
|
+
try {
|
|
32659
|
+
const writer = await JournalWriter.open(tempDir);
|
|
32660
|
+
try {
|
|
32661
|
+
await appendMessages(writer, sessionId, null, history);
|
|
32662
|
+
} finally {
|
|
32663
|
+
await writer.close();
|
|
32664
|
+
}
|
|
32665
|
+
const tempAttachments = attachmentsDirPath(tempDir);
|
|
32666
|
+
let names = [];
|
|
32667
|
+
try {
|
|
32668
|
+
names = await readdir(tempAttachments);
|
|
32669
|
+
} catch {
|
|
32670
|
+
names = [];
|
|
32671
|
+
}
|
|
32672
|
+
if (names.length > 0) {
|
|
32673
|
+
const target = attachmentsDirPath(sessionDir);
|
|
32674
|
+
await mkdir3(target, { recursive: true });
|
|
32675
|
+
for (const name of names) {
|
|
32676
|
+
await rename2(path22.join(tempAttachments, name), path22.join(target, name));
|
|
32677
|
+
}
|
|
32678
|
+
}
|
|
32679
|
+
await rename2(journalFilePath(tempDir), journalFilePath(sessionDir));
|
|
32680
|
+
} finally {
|
|
32681
|
+
await rm3(tempDir, { recursive: true, force: true }).catch(() => void 0);
|
|
32682
|
+
}
|
|
32683
|
+
}
|
|
32684
|
+
return {
|
|
32685
|
+
async create(init) {
|
|
32686
|
+
const sessionId = init.sessionId ?? randomUUID12();
|
|
32687
|
+
return enqueue(sessionId, async () => {
|
|
32688
|
+
const { sessionDir, created } = await ensureSessionDir({
|
|
32689
|
+
storageRoot,
|
|
32690
|
+
sessionId,
|
|
32691
|
+
cwd: process.cwd(),
|
|
32692
|
+
...init.title !== void 0 ? { extra: { title: init.title } } : {}
|
|
32693
|
+
});
|
|
32694
|
+
const raw = await patchRawMeta(sessionDir, (m) => {
|
|
32695
|
+
let changed = false;
|
|
32696
|
+
if (created && typeof m["updatedAt"] !== "string") {
|
|
32697
|
+
m["updatedAt"] = typeof m["createdAt"] === "string" ? m["createdAt"] : (/* @__PURE__ */ new Date()).toISOString();
|
|
32698
|
+
changed = true;
|
|
32699
|
+
}
|
|
32700
|
+
if (init.platformSessionId !== void 0 && m["platformSessionId"] !== init.platformSessionId) {
|
|
32701
|
+
m["platformSessionId"] = init.platformSessionId;
|
|
32702
|
+
changed = true;
|
|
32703
|
+
}
|
|
32704
|
+
if (init.title !== void 0 && m["title"] !== init.title) {
|
|
32705
|
+
m["title"] = init.title;
|
|
32706
|
+
changed = true;
|
|
32707
|
+
}
|
|
32708
|
+
if (init.meta !== void 0 && created) {
|
|
32709
|
+
m["meta"] = init.meta;
|
|
32710
|
+
changed = true;
|
|
32711
|
+
}
|
|
32712
|
+
return changed;
|
|
32713
|
+
});
|
|
32714
|
+
if (raw === null) throw corrupted(sessionId, "meta.json unreadable right after creation");
|
|
32715
|
+
return toRecordMeta(raw);
|
|
32716
|
+
});
|
|
32717
|
+
},
|
|
32718
|
+
async get(sessionId) {
|
|
32719
|
+
return enqueue(sessionId, async () => {
|
|
32720
|
+
const sessionDir = sessionDirPath(storageRoot, sessionId);
|
|
32721
|
+
const raw = await readRawMeta(sessionDir);
|
|
32722
|
+
if (raw === null) return null;
|
|
32723
|
+
const lock = await acquireSessionLock(sessionDir);
|
|
32724
|
+
try {
|
|
32725
|
+
let result;
|
|
32726
|
+
try {
|
|
32727
|
+
result = await readAll(sessionDir);
|
|
32728
|
+
} catch (err) {
|
|
32729
|
+
throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
|
|
32730
|
+
}
|
|
32731
|
+
if (result.integrityBreakAt !== void 0) {
|
|
32732
|
+
throw corrupted(
|
|
32733
|
+
sessionId,
|
|
32734
|
+
`journal hash chain broken at record index ${result.integrityBreakAt}`
|
|
32735
|
+
);
|
|
32736
|
+
}
|
|
32737
|
+
const state = rebuildState(result.records);
|
|
32738
|
+
if (state.warnings.length > 0) {
|
|
32739
|
+
throw corrupted(sessionId, state.warnings.join("; "));
|
|
32740
|
+
}
|
|
32741
|
+
return { meta: toRecordMeta(raw), messages: state.messages };
|
|
32742
|
+
} finally {
|
|
32743
|
+
await lock.release();
|
|
32744
|
+
}
|
|
32745
|
+
});
|
|
32746
|
+
},
|
|
32747
|
+
async list(filter) {
|
|
32748
|
+
const root = path22.join(storageRoot, "sessions");
|
|
32749
|
+
let entries;
|
|
32750
|
+
try {
|
|
32751
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
32752
|
+
} catch {
|
|
32753
|
+
return [];
|
|
32754
|
+
}
|
|
32755
|
+
const records = [];
|
|
32756
|
+
for (const entry of entries) {
|
|
32757
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
32758
|
+
const raw = await readRawMeta(path22.join(root, entry.name));
|
|
32759
|
+
if (raw === null || typeof raw["sessionId"] !== "string") continue;
|
|
32760
|
+
records.push(toRecordMeta(raw));
|
|
32761
|
+
}
|
|
32762
|
+
records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
32763
|
+
return filter?.limit !== void 0 ? records.slice(0, filter.limit) : records;
|
|
32764
|
+
},
|
|
32765
|
+
async commit(sessionId, history, meta) {
|
|
32766
|
+
const snapshot = history.map((m) => structuredClone(m));
|
|
32767
|
+
return enqueue(sessionId, async () => {
|
|
32768
|
+
const title = sessionTitleOf(snapshot);
|
|
32769
|
+
const { sessionDir } = await ensureSessionDir({
|
|
32770
|
+
storageRoot,
|
|
32771
|
+
sessionId,
|
|
32772
|
+
cwd: process.cwd(),
|
|
32773
|
+
...title !== void 0 ? { extra: { title } } : {}
|
|
32774
|
+
});
|
|
32775
|
+
const lock = await acquireSessionLock(sessionDir);
|
|
32776
|
+
try {
|
|
32777
|
+
let result;
|
|
32778
|
+
try {
|
|
32779
|
+
result = await readAll(sessionDir, { rehydrateImages: false });
|
|
32780
|
+
} catch (err) {
|
|
32781
|
+
throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
|
|
32782
|
+
}
|
|
32783
|
+
if (result.integrityBreakAt !== void 0) {
|
|
32784
|
+
throw corrupted(
|
|
32785
|
+
sessionId,
|
|
32786
|
+
`journal hash chain broken at record index ${result.integrityBreakAt}`
|
|
32787
|
+
);
|
|
32788
|
+
}
|
|
32789
|
+
const persisted = result.records.filter(
|
|
32790
|
+
(r) => r.branchId === MAIN_BRANCH_ID && MESSAGE_KINDS2.has(r.kind) && r.visibility.model
|
|
32791
|
+
).length;
|
|
32792
|
+
if (meta.rewritten || snapshot.length < persisted) {
|
|
32793
|
+
await rotateVolume(sessionDir, sessionId, snapshot);
|
|
32794
|
+
} else {
|
|
32795
|
+
const delta = snapshot.slice(persisted);
|
|
32796
|
+
if (delta.length > 0) {
|
|
32797
|
+
const writer = await JournalWriter.open(sessionDir);
|
|
32798
|
+
try {
|
|
32799
|
+
const last = result.records[result.records.length - 1];
|
|
32800
|
+
await appendMessages(writer, sessionId, last?.eventId ?? null, delta);
|
|
32801
|
+
} finally {
|
|
32802
|
+
await writer.close();
|
|
32803
|
+
}
|
|
32804
|
+
}
|
|
32805
|
+
}
|
|
32806
|
+
await patchRawMeta(sessionDir, (m) => {
|
|
32807
|
+
m["updatedAt"] = (/* @__PURE__ */ new Date()).toISOString();
|
|
32808
|
+
return true;
|
|
32809
|
+
});
|
|
32810
|
+
} finally {
|
|
32811
|
+
await lock.release();
|
|
32812
|
+
}
|
|
32813
|
+
});
|
|
32814
|
+
},
|
|
32815
|
+
async delete(sessionId) {
|
|
32816
|
+
return enqueue(sessionId, async () => {
|
|
32817
|
+
await rm3(sessionDirPath(storageRoot, sessionId), { recursive: true, force: true });
|
|
32818
|
+
});
|
|
32819
|
+
}
|
|
32820
|
+
};
|
|
32821
|
+
}
|
|
32822
|
+
|
|
31773
32823
|
// src/view/reducer.ts
|
|
31774
32824
|
var OUTPUT_TAIL_MAX_CHARS = 4e3;
|
|
31775
32825
|
function initialSessionViewState() {
|
|
@@ -32367,6 +33417,58 @@ function markSourceFailure(state, error) {
|
|
|
32367
33417
|
});
|
|
32368
33418
|
}
|
|
32369
33419
|
|
|
33420
|
+
// src/view/history.ts
|
|
33421
|
+
function viewStateFromHistory(messages) {
|
|
33422
|
+
const results = /* @__PURE__ */ new Map();
|
|
33423
|
+
for (const message of messages) {
|
|
33424
|
+
for (const block of message.blocks) {
|
|
33425
|
+
if (block.t === "tool_result") results.set(block.callId, block);
|
|
33426
|
+
}
|
|
33427
|
+
}
|
|
33428
|
+
const uiMessages = [];
|
|
33429
|
+
let nextSeq = 1;
|
|
33430
|
+
for (const message of messages) {
|
|
33431
|
+
const parts = [];
|
|
33432
|
+
for (const block of message.blocks) {
|
|
33433
|
+
switch (block.t) {
|
|
33434
|
+
case "text":
|
|
33435
|
+
if (block.text !== "") parts.push({ type: "text", text: block.text });
|
|
33436
|
+
break;
|
|
33437
|
+
case "thinking":
|
|
33438
|
+
if (block.text !== "") parts.push({ type: "thinking", text: block.text });
|
|
33439
|
+
break;
|
|
33440
|
+
case "tool_call": {
|
|
33441
|
+
const result = results.get(block.id);
|
|
33442
|
+
const resultText = result?.content.filter((item) => item.t === "text").map((item) => item.text ?? "").join("\n");
|
|
33443
|
+
const status = result === void 0 ? "aborted" : result.isError === true ? "failed" : "completed";
|
|
33444
|
+
const part = {
|
|
33445
|
+
type: "toolCall",
|
|
33446
|
+
id: block.id,
|
|
33447
|
+
name: block.name,
|
|
33448
|
+
// 键在场性对齐 Kotlin(JsonElement?):args 键在场(含显式 null)
|
|
33449
|
+
// 即透传,键缺席即不落
|
|
33450
|
+
...block.args !== void 0 ? { args: block.args } : {},
|
|
33451
|
+
status,
|
|
33452
|
+
...resultText !== void 0 && resultText !== "" ? { resultText } : {}
|
|
33453
|
+
};
|
|
33454
|
+
parts.push(part);
|
|
33455
|
+
break;
|
|
33456
|
+
}
|
|
33457
|
+
default:
|
|
33458
|
+
break;
|
|
33459
|
+
}
|
|
33460
|
+
}
|
|
33461
|
+
if (parts.length === 0) continue;
|
|
33462
|
+
uiMessages.push({ id: `msg-${nextSeq}`, role: message.role, parts });
|
|
33463
|
+
nextSeq += 1;
|
|
33464
|
+
}
|
|
33465
|
+
const initial = initialSessionViewState();
|
|
33466
|
+
return {
|
|
33467
|
+
view: { ...initial.view, messages: uiMessages },
|
|
33468
|
+
internal: { ...initial.internal, nextMessageSeq: nextSeq }
|
|
33469
|
+
};
|
|
33470
|
+
}
|
|
33471
|
+
|
|
32370
33472
|
// src/view/pump.ts
|
|
32371
33473
|
function resolveIterable(source) {
|
|
32372
33474
|
if (Symbol.asyncIterator in source) return source;
|
|
@@ -32600,6 +33702,7 @@ export {
|
|
|
32600
33702
|
ImageGenArgsSchema,
|
|
32601
33703
|
McpHost,
|
|
32602
33704
|
OUTPUT_TAIL_MAX_CHARS,
|
|
33705
|
+
PLATFORM_SEARCH_PROVIDER_NAME,
|
|
32603
33706
|
QueueChannel,
|
|
32604
33707
|
SequentialToolExecutor,
|
|
32605
33708
|
TANSR_PROVIDER_ID,
|
|
@@ -32608,8 +33711,6 @@ export {
|
|
|
32608
33711
|
UnavailableChannel,
|
|
32609
33712
|
VIDEOGEN_TOOL_MAX_DURATION,
|
|
32610
33713
|
VideoGenArgsSchema,
|
|
32611
|
-
WEBSEARCH_TOOL_MAX_RESULTS,
|
|
32612
|
-
WebSearchArgsSchema2 as WebSearchArgsSchema,
|
|
32613
33714
|
accumulateUsage,
|
|
32614
33715
|
appBundleRegistryConfig,
|
|
32615
33716
|
appendUserMessage,
|
|
@@ -32623,14 +33724,15 @@ export {
|
|
|
32623
33724
|
buildSdkToolSet,
|
|
32624
33725
|
buildToolGuideSegment,
|
|
32625
33726
|
createAppTokenFetch,
|
|
33727
|
+
createFileSessionStore,
|
|
32626
33728
|
createImageGenTool,
|
|
32627
33729
|
createMcpHost,
|
|
32628
33730
|
createNarrator,
|
|
33731
|
+
createPlatformSearchProvider,
|
|
32629
33732
|
createSdkPermissionGate,
|
|
32630
33733
|
createSession,
|
|
32631
33734
|
createSessionView,
|
|
32632
33735
|
createVideoGenTool,
|
|
32633
|
-
createWebSearchTool2 as createWebSearchTool,
|
|
32634
33736
|
defineSkill,
|
|
32635
33737
|
defineTool,
|
|
32636
33738
|
initialSessionViewState,
|
|
@@ -32644,5 +33746,6 @@ export {
|
|
|
32644
33746
|
resolvePlatformSelection,
|
|
32645
33747
|
runAgent,
|
|
32646
33748
|
subagentModelResolverOf,
|
|
32647
|
-
toToolDef
|
|
33749
|
+
toToolDef,
|
|
33750
|
+
viewStateFromHistory
|
|
32648
33751
|
};
|