@tansr/sdk 0.2.0 → 0.3.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 +314 -103
- package/dist/index.js +1374 -430
- 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";
|
|
@@ -7559,7 +7567,7 @@ async function* consumeModelStream(deps) {
|
|
|
7559
7567
|
const stream = client.stream(request, callOptions);
|
|
7560
7568
|
const collected = /* @__PURE__ */ new Map();
|
|
7561
7569
|
const order = [];
|
|
7562
|
-
const
|
|
7570
|
+
const open4 = /* @__PURE__ */ new Set();
|
|
7563
7571
|
const assembler = new ToolArgsAssembler();
|
|
7564
7572
|
const toolCalls = [];
|
|
7565
7573
|
let stopEvent;
|
|
@@ -7608,7 +7616,7 @@ async function* consumeModelStream(deps) {
|
|
|
7608
7616
|
text: ev.block.t === "text" || ev.block.t === "thinking" ? ev.block.text : ""
|
|
7609
7617
|
});
|
|
7610
7618
|
order.push(ev.index);
|
|
7611
|
-
|
|
7619
|
+
open4.add(ev.index);
|
|
7612
7620
|
yield emit({ type: "msg.block.start", index: ev.index, blockType: ev.block.t });
|
|
7613
7621
|
break;
|
|
7614
7622
|
}
|
|
@@ -7633,7 +7641,7 @@ async function* consumeModelStream(deps) {
|
|
|
7633
7641
|
assembler.append(ev.index, ev.json);
|
|
7634
7642
|
break;
|
|
7635
7643
|
case "block_stop": {
|
|
7636
|
-
|
|
7644
|
+
open4.delete(ev.index);
|
|
7637
7645
|
yield emit({ type: "msg.block.end", index: ev.index });
|
|
7638
7646
|
const c = collected.get(ev.index);
|
|
7639
7647
|
if (c && c.base.t === "tool_call") {
|
|
@@ -7674,16 +7682,16 @@ async function* consumeModelStream(deps) {
|
|
|
7674
7682
|
}
|
|
7675
7683
|
}
|
|
7676
7684
|
}
|
|
7677
|
-
const completedTextBlocks = () => order.filter((index) => !
|
|
7685
|
+
const completedTextBlocks = () => order.filter((index) => !open4.has(index)).map((index) => collected.get(index)).filter(
|
|
7678
7686
|
(c) => c !== void 0 && c.base.t === "text" && c.text.trim().length > 0
|
|
7679
7687
|
).map((c) => finalizeBlock(c));
|
|
7680
7688
|
if (abortedMidStream || signal.aborted) {
|
|
7681
|
-
for (const index of
|
|
7689
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7682
7690
|
disposeGenerator(stream);
|
|
7683
7691
|
return { kind: "aborted" };
|
|
7684
7692
|
}
|
|
7685
7693
|
if (errorEvent) {
|
|
7686
|
-
for (const index of
|
|
7694
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7687
7695
|
disposeGenerator(stream);
|
|
7688
7696
|
return {
|
|
7689
7697
|
kind: "error",
|
|
@@ -7697,7 +7705,7 @@ async function* consumeModelStream(deps) {
|
|
|
7697
7705
|
};
|
|
7698
7706
|
}
|
|
7699
7707
|
if (!stopEvent) {
|
|
7700
|
-
for (const index of
|
|
7708
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7701
7709
|
return {
|
|
7702
7710
|
kind: "error",
|
|
7703
7711
|
error: {
|
|
@@ -7709,7 +7717,7 @@ async function* consumeModelStream(deps) {
|
|
|
7709
7717
|
};
|
|
7710
7718
|
}
|
|
7711
7719
|
if (stopEvent.stopReason === "unknown") {
|
|
7712
|
-
for (const index of
|
|
7720
|
+
for (const index of open4) yield emit({ type: "msg.retracted", index });
|
|
7713
7721
|
disposeGenerator(stream);
|
|
7714
7722
|
return {
|
|
7715
7723
|
kind: "error",
|
|
@@ -9839,16 +9847,16 @@ function unescapeSpecifier(spec) {
|
|
|
9839
9847
|
function parseRule(raw) {
|
|
9840
9848
|
const trimmed = raw.trim();
|
|
9841
9849
|
if (trimmed === "") return null;
|
|
9842
|
-
const
|
|
9843
|
-
if (
|
|
9850
|
+
const open4 = findUnescapedParen(trimmed);
|
|
9851
|
+
if (open4 === -1) {
|
|
9844
9852
|
if (TOOL_NAME_RE.test(trimmed)) return { raw: trimmed, toolName: trimmed };
|
|
9845
9853
|
if (isMcpServerWildcard(trimmed)) return { raw: trimmed, toolName: trimmed };
|
|
9846
9854
|
return null;
|
|
9847
9855
|
}
|
|
9848
9856
|
if (!trimmed.endsWith(")")) return null;
|
|
9849
|
-
const toolName2 = trimmed.slice(0,
|
|
9857
|
+
const toolName2 = trimmed.slice(0, open4);
|
|
9850
9858
|
if (!TOOL_NAME_RE.test(toolName2)) return null;
|
|
9851
|
-
const spec = unescapeSpecifier(trimmed.slice(
|
|
9859
|
+
const spec = unescapeSpecifier(trimmed.slice(open4 + 1, -1)).trim();
|
|
9852
9860
|
if (spec === "" || spec === "*") return { raw: trimmed, toolName: toolName2 };
|
|
9853
9861
|
return { raw: trimmed, toolName: toolName2, specifier: spec };
|
|
9854
9862
|
}
|
|
@@ -10092,9 +10100,9 @@ function stripBom(text) {
|
|
|
10092
10100
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
10093
10101
|
}
|
|
10094
10102
|
function stripFrontmatter(text) {
|
|
10095
|
-
const
|
|
10096
|
-
if (
|
|
10097
|
-
const rest = text.slice(
|
|
10103
|
+
const open4 = /^---[ \t]*\r?\n/.exec(text);
|
|
10104
|
+
if (open4 === null) return text;
|
|
10105
|
+
const rest = text.slice(open4[0].length);
|
|
10098
10106
|
const close = /^---[ \t]*(?:\r?\n|$)/m.exec(rest);
|
|
10099
10107
|
if (close === null) return text;
|
|
10100
10108
|
return rest.slice(close.index + close[0].length);
|
|
@@ -10161,9 +10169,9 @@ var TYPE_KEY_RE = /^type[ \t]*:[ \t]*(.*)$/;
|
|
|
10161
10169
|
function memoryEntryTypeOf(content, indexFile) {
|
|
10162
10170
|
if (indexFile) return "unknown";
|
|
10163
10171
|
const text = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
10164
|
-
const
|
|
10165
|
-
if (
|
|
10166
|
-
const rest = text.slice(
|
|
10172
|
+
const open4 = FRONTMATTER_OPEN_RE.exec(text);
|
|
10173
|
+
if (open4 === null) return "unknown";
|
|
10174
|
+
const rest = text.slice(open4[0].length);
|
|
10167
10175
|
const close = FRONTMATTER_CLOSE_RE.exec(rest);
|
|
10168
10176
|
if (close === null) return "unknown";
|
|
10169
10177
|
for (const rawLine of rest.slice(0, close.index).split("\n")) {
|
|
@@ -10651,6 +10659,7 @@ var CLASSIFIER_BREAKER_TOTAL_LIMIT = 20;
|
|
|
10651
10659
|
var CLASSIFIER_BREAKER_COOLDOWN_MS = 30 * 6e4;
|
|
10652
10660
|
|
|
10653
10661
|
// ../kernel/src/journal/hash.ts
|
|
10662
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
10654
10663
|
function sortKeysDeep2(value) {
|
|
10655
10664
|
if (Array.isArray(value)) return value.map(sortKeysDeep2);
|
|
10656
10665
|
if (value !== null && typeof value === "object") {
|
|
@@ -10667,6 +10676,11 @@ function canonicalStringify(value) {
|
|
|
10667
10676
|
const normalized = JSON.parse(JSON.stringify(value));
|
|
10668
10677
|
return JSON.stringify(sortKeysDeep2(normalized));
|
|
10669
10678
|
}
|
|
10679
|
+
function computeRecordHash(record) {
|
|
10680
|
+
const { integrity, ...rest } = record;
|
|
10681
|
+
const hashable = { ...rest, integrity: { previousHash: integrity.previousHash } };
|
|
10682
|
+
return createHash5("sha256").update(canonicalStringify(hashable), "utf8").digest("hex");
|
|
10683
|
+
}
|
|
10670
10684
|
|
|
10671
10685
|
// ../kernel/src/permissions/broad-allow.ts
|
|
10672
10686
|
var BROAD_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
@@ -11146,13 +11160,13 @@ function canonicalCwd(cwd) {
|
|
|
11146
11160
|
const resolved = resolveForPermission(cwd, ".");
|
|
11147
11161
|
return resolved.kind === "ok" ? resolved.path : null;
|
|
11148
11162
|
}
|
|
11149
|
-
function canonicalPathEligible(cwdCanon,
|
|
11150
|
-
if (hasWindowsDeviceSegment(
|
|
11151
|
-
if (
|
|
11152
|
-
if (!
|
|
11153
|
-
if (guard.isProtectedContentPath(
|
|
11154
|
-
if (matchProtectedPath(
|
|
11155
|
-
const segments =
|
|
11163
|
+
function canonicalPathEligible(cwdCanon, path23, policy, guard) {
|
|
11164
|
+
if (hasWindowsDeviceSegment(path23)) return false;
|
|
11165
|
+
if (path23 === cwdCanon) return !policy.strict;
|
|
11166
|
+
if (!path23.startsWith(`${cwdCanon}/`)) return false;
|
|
11167
|
+
if (guard.isProtectedContentPath(path23)) return false;
|
|
11168
|
+
if (matchProtectedPath(path23, null) !== null) return false;
|
|
11169
|
+
const segments = path23.slice(cwdCanon.length + 1).split("/");
|
|
11156
11170
|
for (const [index, seg] of segments.entries()) {
|
|
11157
11171
|
const hasGlob = GLOB_CHAR_RE.test(seg);
|
|
11158
11172
|
if (hasGlob && seg.startsWith(".")) return false;
|
|
@@ -11336,9 +11350,9 @@ function verifyBundle(bundle, opts = {}) {
|
|
|
11336
11350
|
// ../kernel/src/permissions/soften-radius.ts
|
|
11337
11351
|
var PROTECTED_PATH_RULE_PREFIX = "protected-path(";
|
|
11338
11352
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
11339
|
-
function pathInsideRoot(
|
|
11353
|
+
function pathInsideRoot(path23, root) {
|
|
11340
11354
|
if (root === null) return false;
|
|
11341
|
-
return
|
|
11355
|
+
return path23 === root || path23.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
11342
11356
|
}
|
|
11343
11357
|
function classifySoftenFace(input, base) {
|
|
11344
11358
|
if (base.matchedRule?.startsWith(PROTECTED_PATH_RULE_PREFIX) === true) return "F6";
|
|
@@ -12642,8 +12656,8 @@ function fsErrorCode(err) {
|
|
|
12642
12656
|
}
|
|
12643
12657
|
|
|
12644
12658
|
// ../kernel/src/tools/files/real-target-guard.ts
|
|
12645
|
-
function accessPhrases(
|
|
12646
|
-
return
|
|
12659
|
+
function accessPhrases(access2) {
|
|
12660
|
+
return access2 === "read" ? { doing: "reading from", refused: "Reading is refused" } : { doing: "writing to", refused: "Writing is refused" };
|
|
12647
12661
|
}
|
|
12648
12662
|
async function findExistingAnchor(fs3, abs) {
|
|
12649
12663
|
let cur = abs;
|
|
@@ -12670,8 +12684,8 @@ function insideCanonical(child, base, caseInsensitive) {
|
|
|
12670
12684
|
const b = caseInsensitive ? base.toLowerCase() : base;
|
|
12671
12685
|
return c === b || c.startsWith(b.endsWith("/") ? b : `${b}/`);
|
|
12672
12686
|
}
|
|
12673
|
-
function refusal(toolName2, declared, realTarget, code, detail,
|
|
12674
|
-
const phrase = accessPhrases(
|
|
12687
|
+
function refusal(toolName2, declared, realTarget, code, detail, access2) {
|
|
12688
|
+
const phrase = accessPhrases(access2);
|
|
12675
12689
|
return {
|
|
12676
12690
|
ok: false,
|
|
12677
12691
|
code,
|
|
@@ -12681,8 +12695,8 @@ function refusal(toolName2, declared, realTarget, code, detail, access) {
|
|
|
12681
12695
|
}
|
|
12682
12696
|
async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
12683
12697
|
const caseInsensitive = options.caseInsensitivePaths ?? process.platform === "win32";
|
|
12684
|
-
const
|
|
12685
|
-
const phrase = accessPhrases(
|
|
12698
|
+
const access2 = options.access ?? "write";
|
|
12699
|
+
const phrase = accessPhrases(access2);
|
|
12686
12700
|
const fs3 = options.fs ?? defaultFs;
|
|
12687
12701
|
let anchor;
|
|
12688
12702
|
try {
|
|
@@ -12720,7 +12734,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12720
12734
|
realTarget,
|
|
12721
12735
|
"unverifiable",
|
|
12722
12736
|
"The redirected target cannot be safely canonicalized for comparison.",
|
|
12723
|
-
|
|
12737
|
+
access2
|
|
12724
12738
|
);
|
|
12725
12739
|
}
|
|
12726
12740
|
const memoryDirs = options.memoryCarveOutDirs ?? [];
|
|
@@ -12766,7 +12780,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12766
12780
|
realTarget,
|
|
12767
12781
|
"memory_divergence",
|
|
12768
12782
|
"Memory files must be plain files: no path component of a memory target may be a symlink or junction.",
|
|
12769
|
-
|
|
12783
|
+
access2
|
|
12770
12784
|
);
|
|
12771
12785
|
}
|
|
12772
12786
|
} else if (realInMemory) {
|
|
@@ -12776,7 +12790,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12776
12790
|
realTarget,
|
|
12777
12791
|
"memory_divergence",
|
|
12778
12792
|
"The real target is inside the agent memory directory, which may only be addressed directly.",
|
|
12779
|
-
|
|
12793
|
+
access2
|
|
12780
12794
|
);
|
|
12781
12795
|
}
|
|
12782
12796
|
}
|
|
@@ -12798,7 +12812,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12798
12812
|
realTarget,
|
|
12799
12813
|
"escapes_workspace",
|
|
12800
12814
|
`The real target is outside the workspace root "${options.cwd}".`,
|
|
12801
|
-
|
|
12815
|
+
access2
|
|
12802
12816
|
);
|
|
12803
12817
|
}
|
|
12804
12818
|
const homeC = canonicalHomedir(options.homedir ?? os.homedir());
|
|
@@ -12811,7 +12825,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12811
12825
|
realTarget,
|
|
12812
12826
|
"protected_divergence",
|
|
12813
12827
|
`The real target is inside a protected area (${realLabel}).`,
|
|
12814
|
-
|
|
12828
|
+
access2
|
|
12815
12829
|
);
|
|
12816
12830
|
}
|
|
12817
12831
|
if (realC.kind === "ok" && matchesSecretFace(realC.path) && !(declaredC.kind === "ok" && matchesSecretFace(declaredC.path))) {
|
|
@@ -12821,7 +12835,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
|
|
|
12821
12835
|
realTarget,
|
|
12822
12836
|
"secret_divergence",
|
|
12823
12837
|
"The real target matches a protected secret-file pattern.",
|
|
12824
|
-
|
|
12838
|
+
access2
|
|
12825
12839
|
);
|
|
12826
12840
|
}
|
|
12827
12841
|
return { ok: true, realPath: realTarget, linkResolved: true };
|
|
@@ -13714,7 +13728,31 @@ var DispatchingToolExecutor = class {
|
|
|
13714
13728
|
}
|
|
13715
13729
|
};
|
|
13716
13730
|
|
|
13731
|
+
// ../kernel/src/journal/errors.ts
|
|
13732
|
+
var JournalError = class extends Error {
|
|
13733
|
+
code;
|
|
13734
|
+
constructor(code, message) {
|
|
13735
|
+
super(message);
|
|
13736
|
+
this.name = "JournalError";
|
|
13737
|
+
this.code = code;
|
|
13738
|
+
}
|
|
13739
|
+
};
|
|
13740
|
+
function isErrnoException(err) {
|
|
13741
|
+
return err instanceof Error && typeof err.code === "string";
|
|
13742
|
+
}
|
|
13743
|
+
var JournalCorruptionError = class extends JournalError {
|
|
13744
|
+
/** 损坏所在行号(1-based) */
|
|
13745
|
+
line;
|
|
13746
|
+
constructor(line, detail) {
|
|
13747
|
+
super("JOURNAL_CORRUPTED", `journal 第 ${line} 行数据损坏:${detail}`);
|
|
13748
|
+
this.name = "JournalCorruptionError";
|
|
13749
|
+
this.line = line;
|
|
13750
|
+
}
|
|
13751
|
+
};
|
|
13752
|
+
|
|
13717
13753
|
// ../kernel/src/journal/paths.ts
|
|
13754
|
+
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
|
|
13755
|
+
import path5 from "node:path";
|
|
13718
13756
|
import { z as z14 } from "zod";
|
|
13719
13757
|
var SessionMetaSchema = z14.object({
|
|
13720
13758
|
schemaVersion: z14.literal(1),
|
|
@@ -13744,12 +13782,543 @@ var SessionMetaSchema = z14.object({
|
|
|
13744
13782
|
*/
|
|
13745
13783
|
outputStyle: z14.string().optional()
|
|
13746
13784
|
});
|
|
13785
|
+
var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13786
|
+
function assertValidSessionId(sessionId) {
|
|
13787
|
+
if (!SESSION_ID_PATTERN.test(sessionId)) {
|
|
13788
|
+
throw new JournalError(
|
|
13789
|
+
"INVALID_SESSION_ID",
|
|
13790
|
+
`非法 sessionId ${JSON.stringify(sessionId)}:仅允许字母数字与 . _ -,且须以字母数字开头`
|
|
13791
|
+
);
|
|
13792
|
+
}
|
|
13793
|
+
}
|
|
13794
|
+
function sessionDirPath(storageRoot, sessionId) {
|
|
13795
|
+
assertValidSessionId(sessionId);
|
|
13796
|
+
return path5.join(storageRoot, "sessions", sessionId);
|
|
13797
|
+
}
|
|
13798
|
+
function journalFilePath(sessionDir) {
|
|
13799
|
+
return path5.join(sessionDir, "journal.jsonl");
|
|
13800
|
+
}
|
|
13801
|
+
function metaFilePath(sessionDir) {
|
|
13802
|
+
return path5.join(sessionDir, "meta.json");
|
|
13803
|
+
}
|
|
13804
|
+
function lockFilePath(sessionDir) {
|
|
13805
|
+
return path5.join(sessionDir, "lock");
|
|
13806
|
+
}
|
|
13807
|
+
var durableTmpSeq = 0;
|
|
13808
|
+
async function writeFileDurable(filePath, content) {
|
|
13809
|
+
durableTmpSeq += 1;
|
|
13810
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${durableTmpSeq.toString(36)}-${Date.now()}`;
|
|
13811
|
+
const handle = await open(tempPath, "w");
|
|
13812
|
+
try {
|
|
13813
|
+
if (typeof content === "string") {
|
|
13814
|
+
await handle.writeFile(content, "utf8");
|
|
13815
|
+
} else {
|
|
13816
|
+
await handle.writeFile(content);
|
|
13817
|
+
}
|
|
13818
|
+
await handle.sync();
|
|
13819
|
+
} finally {
|
|
13820
|
+
await handle.close();
|
|
13821
|
+
}
|
|
13822
|
+
try {
|
|
13823
|
+
await rename(tempPath, filePath);
|
|
13824
|
+
} catch (err) {
|
|
13825
|
+
await rm(tempPath, { force: true }).catch(() => void 0);
|
|
13826
|
+
throw err;
|
|
13827
|
+
}
|
|
13828
|
+
}
|
|
13829
|
+
async function readSessionMeta(sessionDir) {
|
|
13830
|
+
const raw = await readFile(metaFilePath(sessionDir), "utf8");
|
|
13831
|
+
let parsed;
|
|
13832
|
+
try {
|
|
13833
|
+
parsed = JSON.parse(raw);
|
|
13834
|
+
} catch {
|
|
13835
|
+
throw new JournalError("META_INVALID", `meta.json 不是合法 JSON:${metaFilePath(sessionDir)}`);
|
|
13836
|
+
}
|
|
13837
|
+
const result = SessionMetaSchema.safeParse(parsed);
|
|
13838
|
+
if (!result.success) {
|
|
13839
|
+
throw new JournalError("META_INVALID", `meta.json 不符合 SessionMeta schema:${result.error.message}`);
|
|
13840
|
+
}
|
|
13841
|
+
return result.data;
|
|
13842
|
+
}
|
|
13843
|
+
async function ensureSessionDir(options) {
|
|
13844
|
+
const sessionDir = sessionDirPath(options.storageRoot, options.sessionId);
|
|
13845
|
+
await mkdir(sessionDir, { recursive: true });
|
|
13846
|
+
try {
|
|
13847
|
+
const meta2 = await readSessionMeta(sessionDir);
|
|
13848
|
+
if (meta2.sessionId !== options.sessionId) {
|
|
13849
|
+
throw new JournalError(
|
|
13850
|
+
"META_MISMATCH",
|
|
13851
|
+
`meta.json 中 sessionId(${meta2.sessionId})与目录名(${options.sessionId})不一致`
|
|
13852
|
+
);
|
|
13853
|
+
}
|
|
13854
|
+
return { sessionDir, meta: meta2, created: false };
|
|
13855
|
+
} catch (err) {
|
|
13856
|
+
if (!isErrnoException(err) || err.code !== "ENOENT") throw err;
|
|
13857
|
+
}
|
|
13858
|
+
const meta = {
|
|
13859
|
+
schemaVersion: 1,
|
|
13860
|
+
sessionId: options.sessionId,
|
|
13861
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13862
|
+
cwd: options.cwd
|
|
13863
|
+
};
|
|
13864
|
+
if (options.extra?.title !== void 0) meta.title = options.extra.title;
|
|
13865
|
+
if (options.extra?.firstUserMessage !== void 0) {
|
|
13866
|
+
meta.firstUserMessage = options.extra.firstUserMessage;
|
|
13867
|
+
}
|
|
13868
|
+
if (options.extra?.model !== void 0) meta.model = options.extra.model;
|
|
13869
|
+
if (options.extra?.provider !== void 0) meta.provider = options.extra.provider;
|
|
13870
|
+
if (options.extra?.outputStyle !== void 0) meta.outputStyle = options.extra.outputStyle;
|
|
13871
|
+
await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(meta, null, 2)}
|
|
13872
|
+
`);
|
|
13873
|
+
return { sessionDir, meta, created: true };
|
|
13874
|
+
}
|
|
13747
13875
|
|
|
13748
13876
|
// ../kernel/src/journal/lock.ts
|
|
13877
|
+
import { open as open2, rm as rm2, stat, readFile as readFile2 } from "node:fs/promises";
|
|
13878
|
+
import os2 from "node:os";
|
|
13749
13879
|
var DEFAULT_STALE_MS = 30 * 60 * 1e3;
|
|
13880
|
+
function defaultIsPidAlive(pid) {
|
|
13881
|
+
try {
|
|
13882
|
+
process.kill(pid, 0);
|
|
13883
|
+
return true;
|
|
13884
|
+
} catch (err) {
|
|
13885
|
+
if (isErrnoException(err) && err.code === "EPERM") return true;
|
|
13886
|
+
return false;
|
|
13887
|
+
}
|
|
13888
|
+
}
|
|
13889
|
+
async function readLockPayload(lockPath) {
|
|
13890
|
+
try {
|
|
13891
|
+
const raw = await readFile2(lockPath, "utf8");
|
|
13892
|
+
const parsed = JSON.parse(raw);
|
|
13893
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.pid === "number") {
|
|
13894
|
+
return parsed;
|
|
13895
|
+
}
|
|
13896
|
+
return null;
|
|
13897
|
+
} catch {
|
|
13898
|
+
return null;
|
|
13899
|
+
}
|
|
13900
|
+
}
|
|
13901
|
+
async function tryCreateLockFile(lockPath) {
|
|
13902
|
+
let handle;
|
|
13903
|
+
try {
|
|
13904
|
+
handle = await open2(lockPath, "wx");
|
|
13905
|
+
} catch (err) {
|
|
13906
|
+
if (isErrnoException(err) && err.code === "EEXIST") return false;
|
|
13907
|
+
throw err;
|
|
13908
|
+
}
|
|
13909
|
+
try {
|
|
13910
|
+
const payload = {
|
|
13911
|
+
pid: process.pid,
|
|
13912
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13913
|
+
host: os2.hostname()
|
|
13914
|
+
};
|
|
13915
|
+
await handle.writeFile(`${JSON.stringify(payload)}
|
|
13916
|
+
`, "utf8");
|
|
13917
|
+
await handle.sync();
|
|
13918
|
+
} finally {
|
|
13919
|
+
await handle.close();
|
|
13920
|
+
}
|
|
13921
|
+
return true;
|
|
13922
|
+
}
|
|
13923
|
+
async function acquireSessionLock(sessionDir, options = {}) {
|
|
13924
|
+
const lockPath = lockFilePath(sessionDir);
|
|
13925
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
13926
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
13927
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
13928
|
+
if (await tryCreateLockFile(lockPath)) {
|
|
13929
|
+
let released = false;
|
|
13930
|
+
return {
|
|
13931
|
+
lockPath,
|
|
13932
|
+
async release() {
|
|
13933
|
+
if (released) return;
|
|
13934
|
+
released = true;
|
|
13935
|
+
await rm2(lockPath, { force: true });
|
|
13936
|
+
}
|
|
13937
|
+
};
|
|
13938
|
+
}
|
|
13939
|
+
const payload = await readLockPayload(lockPath);
|
|
13940
|
+
let mtimeMs = null;
|
|
13941
|
+
try {
|
|
13942
|
+
mtimeMs = (await stat(lockPath)).mtimeMs;
|
|
13943
|
+
} catch (err) {
|
|
13944
|
+
if (isErrnoException(err) && err.code === "ENOENT") {
|
|
13945
|
+
continue;
|
|
13946
|
+
}
|
|
13947
|
+
throw err;
|
|
13948
|
+
}
|
|
13949
|
+
const holderAlive = payload !== null && isPidAlive(payload.pid);
|
|
13950
|
+
const isStale = Date.now() - mtimeMs > staleMs;
|
|
13951
|
+
const canPreempt = payload === null ? isStale : !holderAlive || isStale;
|
|
13952
|
+
if (!canPreempt) {
|
|
13953
|
+
const holder = payload ? `pid=${payload.pid} host=${payload.host} since=${payload.startedAt}` : "未知持有者";
|
|
13954
|
+
throw new JournalError(
|
|
13955
|
+
"SESSION_LOCKED",
|
|
13956
|
+
`会话已被其他进程锁定(${holder}),锁文件:${lockPath}`
|
|
13957
|
+
);
|
|
13958
|
+
}
|
|
13959
|
+
await rm2(lockPath, { force: true });
|
|
13960
|
+
}
|
|
13961
|
+
throw new JournalError(
|
|
13962
|
+
"SESSION_LOCKED",
|
|
13963
|
+
`会话锁竞争激烈,清理陈旧锁后重试仍失败:${lockPath}`
|
|
13964
|
+
);
|
|
13965
|
+
}
|
|
13966
|
+
|
|
13967
|
+
// ../kernel/src/journal/reader.ts
|
|
13968
|
+
import { appendFile, readFile as readFile4, truncate } from "node:fs/promises";
|
|
13969
|
+
|
|
13970
|
+
// ../kernel/src/journal/attachments.ts
|
|
13971
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
13972
|
+
import { access, mkdir as mkdir2, readFile as readFile3 } from "node:fs/promises";
|
|
13973
|
+
import path6 from "node:path";
|
|
13974
|
+
var ATTACHMENTS_DIR_NAME = "attachments";
|
|
13975
|
+
var IMAGE_MESSAGE_KINDS = /* @__PURE__ */ new Set([
|
|
13976
|
+
"user_prompt",
|
|
13977
|
+
"assistant_message",
|
|
13978
|
+
"tool_result"
|
|
13979
|
+
]);
|
|
13980
|
+
var REF_PATTERN = /^sha256:([0-9a-f]{64})$/;
|
|
13981
|
+
function attachmentsDirPath(sessionDir) {
|
|
13982
|
+
return path6.join(sessionDir, ATTACHMENTS_DIR_NAME);
|
|
13983
|
+
}
|
|
13984
|
+
function attachmentFilePath(sessionDir, sha256Hex2) {
|
|
13985
|
+
return path6.join(attachmentsDirPath(sessionDir), sha256Hex2);
|
|
13986
|
+
}
|
|
13987
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13988
|
+
var isInlineImage = (b) => b["t"] === "image" && typeof b["data"] === "string" && typeof b["mime"] === "string" && b["$ref"] === void 0;
|
|
13989
|
+
var isExternalizedImage = (b) => b["t"] === "image" && typeof b["$ref"] === "string" && typeof b["mime"] === "string" && b["data"] === void 0;
|
|
13990
|
+
async function externalizeOne(block, sessionDir) {
|
|
13991
|
+
try {
|
|
13992
|
+
const bytes = Buffer.from(block["data"], "base64");
|
|
13993
|
+
const hex = createHash6("sha256").update(bytes).digest("hex");
|
|
13994
|
+
const file = attachmentFilePath(sessionDir, hex);
|
|
13995
|
+
let exists = true;
|
|
13996
|
+
try {
|
|
13997
|
+
await access(file);
|
|
13998
|
+
} catch {
|
|
13999
|
+
exists = false;
|
|
14000
|
+
}
|
|
14001
|
+
if (!exists) {
|
|
14002
|
+
await mkdir2(attachmentsDirPath(sessionDir), { recursive: true });
|
|
14003
|
+
await writeFileDurable(file, bytes);
|
|
14004
|
+
}
|
|
14005
|
+
const out = {
|
|
14006
|
+
t: "image",
|
|
14007
|
+
mime: block["mime"],
|
|
14008
|
+
$ref: `sha256:${hex}`,
|
|
14009
|
+
bytes: bytes.length
|
|
14010
|
+
};
|
|
14011
|
+
const dims = probeImageDimensions(bytes);
|
|
14012
|
+
if (dims !== null) {
|
|
14013
|
+
out["w"] = dims.width;
|
|
14014
|
+
out["h"] = dims.height;
|
|
14015
|
+
}
|
|
14016
|
+
return out;
|
|
14017
|
+
} catch {
|
|
14018
|
+
return block;
|
|
14019
|
+
}
|
|
14020
|
+
}
|
|
14021
|
+
async function rehydrateOne(block, sessionDir, notes) {
|
|
14022
|
+
const ref = block["$ref"];
|
|
14023
|
+
const placeholder = (noteReason, textReason) => {
|
|
14024
|
+
notes.push(`image 附件${noteReason},块已降级为占位文本(${ref})`);
|
|
14025
|
+
return { t: "text", text: `[image unavailable: attachment ${ref} ${textReason}]` };
|
|
14026
|
+
};
|
|
14027
|
+
const match = REF_PATTERN.exec(ref);
|
|
14028
|
+
if (match === null) return placeholder("引用非法", "has an invalid ref");
|
|
14029
|
+
const hex = match[1];
|
|
14030
|
+
let bytes;
|
|
14031
|
+
try {
|
|
14032
|
+
bytes = await readFile3(attachmentFilePath(sessionDir, hex));
|
|
14033
|
+
} catch {
|
|
14034
|
+
return placeholder("缺失", "is missing");
|
|
14035
|
+
}
|
|
14036
|
+
if (createHash6("sha256").update(bytes).digest("hex") !== hex) {
|
|
14037
|
+
return placeholder("摘要不符", "is corrupt (sha256 mismatch)");
|
|
14038
|
+
}
|
|
14039
|
+
return { t: "image", mime: block["mime"], data: bytes.toString("base64") };
|
|
14040
|
+
}
|
|
14041
|
+
async function mapImageBlocks(payload, match, mapper) {
|
|
14042
|
+
if (!isRecord(payload) || !Array.isArray(payload["blocks"])) return payload;
|
|
14043
|
+
let changed = false;
|
|
14044
|
+
const blocks = [];
|
|
14045
|
+
for (const raw of payload["blocks"]) {
|
|
14046
|
+
if (!isRecord(raw)) {
|
|
14047
|
+
blocks.push(raw);
|
|
14048
|
+
continue;
|
|
14049
|
+
}
|
|
14050
|
+
if (match(raw)) {
|
|
14051
|
+
const mapped = await mapper(raw);
|
|
14052
|
+
if (mapped !== raw) changed = true;
|
|
14053
|
+
blocks.push(mapped);
|
|
14054
|
+
continue;
|
|
14055
|
+
}
|
|
14056
|
+
if (raw["t"] === "tool_result" && Array.isArray(raw["content"])) {
|
|
14057
|
+
let itemChanged = false;
|
|
14058
|
+
const content = [];
|
|
14059
|
+
for (const item of raw["content"]) {
|
|
14060
|
+
if (!isRecord(item) || !match(item)) {
|
|
14061
|
+
content.push(item);
|
|
14062
|
+
continue;
|
|
14063
|
+
}
|
|
14064
|
+
const mapped = await mapper(item);
|
|
14065
|
+
if (mapped !== item) itemChanged = true;
|
|
14066
|
+
content.push(mapped);
|
|
14067
|
+
}
|
|
14068
|
+
if (!itemChanged) {
|
|
14069
|
+
blocks.push(raw);
|
|
14070
|
+
continue;
|
|
14071
|
+
}
|
|
14072
|
+
changed = true;
|
|
14073
|
+
blocks.push({ ...raw, content });
|
|
14074
|
+
continue;
|
|
14075
|
+
}
|
|
14076
|
+
blocks.push(raw);
|
|
14077
|
+
}
|
|
14078
|
+
return changed ? { ...payload, blocks } : payload;
|
|
14079
|
+
}
|
|
14080
|
+
async function externalizeImagePayload(payload, sessionDir) {
|
|
14081
|
+
return mapImageBlocks(payload, isInlineImage, (b) => externalizeOne(b, sessionDir));
|
|
14082
|
+
}
|
|
14083
|
+
async function rehydrateImagePayload(payload, sessionDir, notes) {
|
|
14084
|
+
return mapImageBlocks(payload, isExternalizedImage, (b) => rehydrateOne(b, sessionDir, notes));
|
|
14085
|
+
}
|
|
14086
|
+
|
|
14087
|
+
// ../kernel/src/journal/reader.ts
|
|
14088
|
+
var MAIN_BRANCH_ID = "main";
|
|
14089
|
+
var NEWLINE_BYTE = 10;
|
|
14090
|
+
async function readAll(sessionDir, options = {}) {
|
|
14091
|
+
const filePath = journalFilePath(sessionDir);
|
|
14092
|
+
let buf;
|
|
14093
|
+
try {
|
|
14094
|
+
buf = await readFile4(filePath);
|
|
14095
|
+
} catch (err) {
|
|
14096
|
+
if (isErrnoException(err) && err.code === "ENOENT") return { records: [], repairs: [] };
|
|
14097
|
+
throw err;
|
|
14098
|
+
}
|
|
14099
|
+
const records = [];
|
|
14100
|
+
const lineStartBytes = [];
|
|
14101
|
+
const repairs = [];
|
|
14102
|
+
let truncateTo = null;
|
|
14103
|
+
let missingFinalNewline = false;
|
|
14104
|
+
let pos = 0;
|
|
14105
|
+
let lineNo = 0;
|
|
14106
|
+
while (pos < buf.length) {
|
|
14107
|
+
lineNo++;
|
|
14108
|
+
const nl = buf.indexOf(NEWLINE_BYTE, pos);
|
|
14109
|
+
const hasNewline = nl !== -1;
|
|
14110
|
+
const end = hasNewline ? nl : buf.length;
|
|
14111
|
+
const isLastLine = !hasNewline || nl === buf.length - 1;
|
|
14112
|
+
const text = buf.subarray(pos, end).toString("utf8");
|
|
14113
|
+
let parsed;
|
|
14114
|
+
try {
|
|
14115
|
+
parsed = JSON.parse(text);
|
|
14116
|
+
} catch {
|
|
14117
|
+
if (isLastLine) {
|
|
14118
|
+
truncateTo = pos;
|
|
14119
|
+
repairs.push(`截断崩溃残留的不完整尾行(第 ${lineNo} 行,${buf.length - pos} 字节)`);
|
|
14120
|
+
break;
|
|
14121
|
+
}
|
|
14122
|
+
throw new JournalCorruptionError(lineNo, "JSON 解析失败且非最后一行,无法用崩溃截断解释");
|
|
14123
|
+
}
|
|
14124
|
+
const result = JournalRecordSchema.safeParse(parsed);
|
|
14125
|
+
if (!result.success) {
|
|
14126
|
+
throw new JournalCorruptionError(lineNo, `不符合 JournalRecord schema:${result.error.message}`);
|
|
14127
|
+
}
|
|
14128
|
+
records.push(result.data);
|
|
14129
|
+
lineStartBytes.push(pos);
|
|
14130
|
+
if (!hasNewline) missingFinalNewline = true;
|
|
14131
|
+
pos = end + 1;
|
|
14132
|
+
}
|
|
14133
|
+
while (records.length > 0 && records[records.length - 1].completeness === "partial") {
|
|
14134
|
+
const idx = records.length - 1;
|
|
14135
|
+
truncateTo = lineStartBytes[idx];
|
|
14136
|
+
repairs.push(`截断崩溃残留的 partial 尾记录(seq=${records[idx].seq})`);
|
|
14137
|
+
records.pop();
|
|
14138
|
+
lineStartBytes.pop();
|
|
14139
|
+
missingFinalNewline = false;
|
|
14140
|
+
}
|
|
14141
|
+
if (truncateTo !== null) {
|
|
14142
|
+
await truncate(filePath, truncateTo);
|
|
14143
|
+
} else if (missingFinalNewline) {
|
|
14144
|
+
await appendFile(filePath, "\n");
|
|
14145
|
+
repairs.push("补齐末行缺失的换行符");
|
|
14146
|
+
}
|
|
14147
|
+
let integrityBreakAt;
|
|
14148
|
+
let expectedPrev = null;
|
|
14149
|
+
for (let i = 0; i < records.length; i++) {
|
|
14150
|
+
const rec = records[i];
|
|
14151
|
+
if (rec.integrity.previousHash !== expectedPrev || computeRecordHash(rec) !== rec.integrity.hash) {
|
|
14152
|
+
integrityBreakAt = i;
|
|
14153
|
+
break;
|
|
14154
|
+
}
|
|
14155
|
+
expectedPrev = rec.integrity.hash;
|
|
14156
|
+
}
|
|
14157
|
+
if (options.rehydrateImages !== false) {
|
|
14158
|
+
for (let i = 0; i < records.length; i++) {
|
|
14159
|
+
const rec = records[i];
|
|
14160
|
+
if (!IMAGE_MESSAGE_KINDS.has(rec.kind)) continue;
|
|
14161
|
+
const notes = [];
|
|
14162
|
+
const payload = await rehydrateImagePayload(rec.payload, sessionDir, notes);
|
|
14163
|
+
if (payload !== rec.payload) records[i] = { ...rec, payload };
|
|
14164
|
+
for (const note of notes) repairs.push(`seq=${rec.seq}:${note}`);
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
14167
|
+
return integrityBreakAt === void 0 ? { records, repairs } : { records, integrityBreakAt, repairs };
|
|
14168
|
+
}
|
|
14169
|
+
var MESSAGE_KINDS = /* @__PURE__ */ new Set([
|
|
14170
|
+
"user_prompt",
|
|
14171
|
+
"assistant_message",
|
|
14172
|
+
"tool_result"
|
|
14173
|
+
]);
|
|
14174
|
+
function rebuildState(records) {
|
|
14175
|
+
const messages = [];
|
|
14176
|
+
const warnings = [];
|
|
14177
|
+
for (const rec of records) {
|
|
14178
|
+
if (rec.branchId !== MAIN_BRANCH_ID) continue;
|
|
14179
|
+
if (!MESSAGE_KINDS.has(rec.kind)) continue;
|
|
14180
|
+
if (!rec.visibility.model) continue;
|
|
14181
|
+
const parsed = IRMessageSchema.safeParse(rec.payload);
|
|
14182
|
+
if (!parsed.success) {
|
|
14183
|
+
warnings.push(`seq=${rec.seq}(kind=${rec.kind})的 payload 不是合法 IRMessage,已跳过`);
|
|
14184
|
+
continue;
|
|
14185
|
+
}
|
|
14186
|
+
messages.push(parsed.data);
|
|
14187
|
+
}
|
|
14188
|
+
const last = records.length > 0 ? records[records.length - 1] : void 0;
|
|
14189
|
+
return { messages, lastSeq: last?.seq ?? -1, branchId: MAIN_BRANCH_ID, warnings };
|
|
14190
|
+
}
|
|
14191
|
+
|
|
14192
|
+
// ../kernel/src/journal/writer.ts
|
|
14193
|
+
import { open as open3 } from "node:fs/promises";
|
|
14194
|
+
var CRITICAL_KINDS = /* @__PURE__ */ new Set([
|
|
14195
|
+
"user_prompt",
|
|
14196
|
+
"assistant_message",
|
|
14197
|
+
"tool_result",
|
|
14198
|
+
"fork",
|
|
14199
|
+
"compact_boundary",
|
|
14200
|
+
"queue_enqueue",
|
|
14201
|
+
"queue_ack"
|
|
14202
|
+
]);
|
|
14203
|
+
var JournalWriter = class _JournalWriter {
|
|
14204
|
+
#handle;
|
|
14205
|
+
/** 串行化队列尾:并发 append 按调用序依次执行 */
|
|
14206
|
+
#queue = Promise.resolve();
|
|
14207
|
+
#previousHash;
|
|
14208
|
+
#nextSeq;
|
|
14209
|
+
/** 已确认成功的文件字节长度,写失败时截断回滚的目标 */
|
|
14210
|
+
#byteLength;
|
|
14211
|
+
/** 会话目录(J15 附件外置的落点根) */
|
|
14212
|
+
#sessionDir;
|
|
14213
|
+
#closed = false;
|
|
14214
|
+
/** 回滚失败后置真:磁盘与内存链状态已不可信,拒绝继续写 */
|
|
14215
|
+
#broken = false;
|
|
14216
|
+
constructor(handle, previousHash, nextSeq, byteLength, sessionDir) {
|
|
14217
|
+
this.#handle = handle;
|
|
14218
|
+
this.#previousHash = previousHash;
|
|
14219
|
+
this.#nextSeq = nextSeq;
|
|
14220
|
+
this.#byteLength = byteLength;
|
|
14221
|
+
this.#sessionDir = sessionDir;
|
|
14222
|
+
}
|
|
14223
|
+
/**
|
|
14224
|
+
* 打开(或创建)会话 journal 并定位恢复点:经 readAll 读全量(顺带截断
|
|
14225
|
+
* 崩溃残留、保证行边界干净),取末条记录的 seq/hash 接续哈希链。
|
|
14226
|
+
* 恢复点读取用原始落盘形态(rehydrateImages:false)——只取 seq/hash,
|
|
14227
|
+
* 不为附件回填付费。前置条件:会话目录已存在(经 ensureSessionDir);
|
|
14228
|
+
* 会话锁由调用方持有。
|
|
14229
|
+
*/
|
|
14230
|
+
static async open(sessionDir) {
|
|
14231
|
+
const { records } = await readAll(sessionDir, { rehydrateImages: false });
|
|
14232
|
+
const last = records.length > 0 ? records[records.length - 1] : void 0;
|
|
14233
|
+
const handle = await open3(journalFilePath(sessionDir), "a");
|
|
14234
|
+
try {
|
|
14235
|
+
const { size } = await handle.stat();
|
|
14236
|
+
return new _JournalWriter(handle, last?.integrity.hash ?? null, (last?.seq ?? -1) + 1, size, sessionDir);
|
|
14237
|
+
} catch (err) {
|
|
14238
|
+
await handle.close();
|
|
14239
|
+
throw err;
|
|
14240
|
+
}
|
|
14241
|
+
}
|
|
14242
|
+
/**
|
|
14243
|
+
* 追加一条记录(schema 校验后写);返回实际落盘的完整记录。
|
|
14244
|
+
* 并发调用按发起顺序串行落盘,seq 与文件行序一致。
|
|
14245
|
+
*/
|
|
14246
|
+
append(input) {
|
|
14247
|
+
const task = this.#queue.then(() => this.#appendSerialized(input));
|
|
14248
|
+
this.#queue = task.then(
|
|
14249
|
+
() => void 0,
|
|
14250
|
+
() => void 0
|
|
14251
|
+
);
|
|
14252
|
+
return task;
|
|
14253
|
+
}
|
|
14254
|
+
async #appendSerialized(input) {
|
|
14255
|
+
if (this.#closed) {
|
|
14256
|
+
throw new JournalError("JOURNAL_CLOSED", "JournalWriter 已关闭,不能继续 append");
|
|
14257
|
+
}
|
|
14258
|
+
if (this.#broken) {
|
|
14259
|
+
throw new JournalError("JOURNAL_BROKEN", "此前写失败且截断回滚未成功,writer 已进入不可写状态");
|
|
14260
|
+
}
|
|
14261
|
+
let effective = input;
|
|
14262
|
+
if (IMAGE_MESSAGE_KINDS.has(input.kind)) {
|
|
14263
|
+
const payload = await externalizeImagePayload(input.payload, this.#sessionDir);
|
|
14264
|
+
if (payload !== input.payload) effective = { ...input, payload };
|
|
14265
|
+
}
|
|
14266
|
+
const candidate = {
|
|
14267
|
+
...effective,
|
|
14268
|
+
schemaVersion: 1,
|
|
14269
|
+
seq: this.#nextSeq,
|
|
14270
|
+
completeness: "complete",
|
|
14271
|
+
integrity: { previousHash: this.#previousHash, hash: "" }
|
|
14272
|
+
};
|
|
14273
|
+
const parsed = JournalRecordSchema.safeParse(candidate);
|
|
14274
|
+
if (!parsed.success) {
|
|
14275
|
+
throw new JournalError("RECORD_INVALID", `记录不符合 JournalRecord schema:${parsed.error.message}`);
|
|
14276
|
+
}
|
|
14277
|
+
const record = parsed.data;
|
|
14278
|
+
record.integrity.hash = computeRecordHash(record);
|
|
14279
|
+
const line = Buffer.from(`${JSON.stringify(record)}
|
|
14280
|
+
`, "utf8");
|
|
14281
|
+
try {
|
|
14282
|
+
let written = 0;
|
|
14283
|
+
while (written < line.length) {
|
|
14284
|
+
const { bytesWritten } = await this.#handle.write(line, written, line.length - written);
|
|
14285
|
+
written += bytesWritten;
|
|
14286
|
+
}
|
|
14287
|
+
if (CRITICAL_KINDS.has(record.kind)) {
|
|
14288
|
+
try {
|
|
14289
|
+
await this.#handle.datasync();
|
|
14290
|
+
} catch {
|
|
14291
|
+
await this.#handle.sync();
|
|
14292
|
+
}
|
|
14293
|
+
}
|
|
14294
|
+
} catch (err) {
|
|
14295
|
+
try {
|
|
14296
|
+
await this.#handle.truncate(this.#byteLength);
|
|
14297
|
+
} catch {
|
|
14298
|
+
this.#broken = true;
|
|
14299
|
+
}
|
|
14300
|
+
throw err;
|
|
14301
|
+
}
|
|
14302
|
+
this.#byteLength += line.length;
|
|
14303
|
+
this.#previousHash = record.integrity.hash;
|
|
14304
|
+
this.#nextSeq = record.seq + 1;
|
|
14305
|
+
return record;
|
|
14306
|
+
}
|
|
14307
|
+
/** 下一条将分配的 seq(诊断用) */
|
|
14308
|
+
get nextSeq() {
|
|
14309
|
+
return this.#nextSeq;
|
|
14310
|
+
}
|
|
14311
|
+
/** 等待队列排空并关闭文件句柄;幂等 */
|
|
14312
|
+
async close() {
|
|
14313
|
+
if (this.#closed) return;
|
|
14314
|
+
this.#closed = true;
|
|
14315
|
+
await this.#queue;
|
|
14316
|
+
await this.#handle.close();
|
|
14317
|
+
}
|
|
14318
|
+
};
|
|
13750
14319
|
|
|
13751
14320
|
// ../kernel/src/tools/search/path-utils.ts
|
|
13752
|
-
import * as
|
|
14321
|
+
import * as path7 from "node:path";
|
|
13753
14322
|
function toPosixPath(p) {
|
|
13754
14323
|
return p.replaceAll("\\", "/");
|
|
13755
14324
|
}
|
|
@@ -13757,10 +14326,10 @@ function normalizeDriveLetter(p) {
|
|
|
13757
14326
|
return /^[A-Za-z]:/.test(p) ? p.charAt(0).toLowerCase() + p.slice(1) : p;
|
|
13758
14327
|
}
|
|
13759
14328
|
function isAbsolutePath(p) {
|
|
13760
|
-
return
|
|
14329
|
+
return path7.win32.isAbsolute(p) || path7.posix.isAbsolute(p);
|
|
13761
14330
|
}
|
|
13762
14331
|
function canonicalize(p) {
|
|
13763
|
-
return normalizeDriveLetter(toPosixPath(
|
|
14332
|
+
return normalizeDriveLetter(toPosixPath(path7.resolve(p)));
|
|
13764
14333
|
}
|
|
13765
14334
|
function resolvePathArg(p, cwd) {
|
|
13766
14335
|
const target = p ?? cwd;
|
|
@@ -13813,11 +14382,11 @@ function expandBraces(pattern) {
|
|
|
13813
14382
|
return out;
|
|
13814
14383
|
}
|
|
13815
14384
|
function expandOnce(pattern) {
|
|
13816
|
-
const
|
|
13817
|
-
if (
|
|
14385
|
+
const open4 = pattern.indexOf("{");
|
|
14386
|
+
if (open4 === -1) return null;
|
|
13818
14387
|
let depth = 0;
|
|
13819
14388
|
let close = -1;
|
|
13820
|
-
for (let i =
|
|
14389
|
+
for (let i = open4; i < pattern.length; i++) {
|
|
13821
14390
|
const ch = pattern.charAt(i);
|
|
13822
14391
|
if (ch === "{") depth += 1;
|
|
13823
14392
|
else if (ch === "}") {
|
|
@@ -13829,8 +14398,8 @@ function expandOnce(pattern) {
|
|
|
13829
14398
|
}
|
|
13830
14399
|
}
|
|
13831
14400
|
if (close === -1) return null;
|
|
13832
|
-
const prefix = pattern.slice(0,
|
|
13833
|
-
const body = pattern.slice(
|
|
14401
|
+
const prefix = pattern.slice(0, open4);
|
|
14402
|
+
const body = pattern.slice(open4 + 1, close);
|
|
13834
14403
|
const suffix = pattern.slice(close + 1);
|
|
13835
14404
|
const alternatives = [];
|
|
13836
14405
|
let level = 0;
|
|
@@ -14142,7 +14711,7 @@ var SESSION_EVENT_VOCABULARY = {
|
|
|
14142
14711
|
var SESSION_EVENT_KINDS = Object.keys(SESSION_EVENT_VOCABULARY);
|
|
14143
14712
|
|
|
14144
14713
|
// ../kernel/src/tools/files/read.ts
|
|
14145
|
-
import
|
|
14714
|
+
import path8 from "node:path";
|
|
14146
14715
|
import { z as z15 } from "zod";
|
|
14147
14716
|
|
|
14148
14717
|
// ../kernel/src/tools/files/encoding.ts
|
|
@@ -14224,19 +14793,19 @@ function truncateLine(line) {
|
|
|
14224
14793
|
return `${line.slice(0, cut)}…[line truncated: ${line.length} chars total; use Grep to inspect the rest]`;
|
|
14225
14794
|
}
|
|
14226
14795
|
async function findSimilarFiles(fs3, filePath) {
|
|
14227
|
-
const dir =
|
|
14228
|
-
const targetBase =
|
|
14229
|
-
const targetName =
|
|
14796
|
+
const dir = path8.dirname(filePath);
|
|
14797
|
+
const targetBase = path8.basename(filePath);
|
|
14798
|
+
const targetName = path8.parse(filePath).name.toLowerCase();
|
|
14230
14799
|
try {
|
|
14231
14800
|
const entries = await fs3.readdir(dir);
|
|
14232
14801
|
return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).filter(
|
|
14233
|
-
(name) => name.toLowerCase() !== targetBase.toLowerCase() &&
|
|
14234
|
-
).slice(0, 3).map((name) =>
|
|
14802
|
+
(name) => name.toLowerCase() !== targetBase.toLowerCase() && path8.parse(name).name.toLowerCase() === targetName
|
|
14803
|
+
).slice(0, 3).map((name) => path8.join(dir, name));
|
|
14235
14804
|
} catch {
|
|
14236
14805
|
return [];
|
|
14237
14806
|
}
|
|
14238
14807
|
}
|
|
14239
|
-
async function executeImageRead(resolved, args, ctx, fs3,
|
|
14808
|
+
async function executeImageRead(resolved, args, ctx, fs3, stat2, options) {
|
|
14240
14809
|
let mode;
|
|
14241
14810
|
try {
|
|
14242
14811
|
mode = options.imageInput?.();
|
|
@@ -14255,9 +14824,9 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14255
14824
|
"offset/limit apply to text files only. Call Read again without offset/limit to read this image file."
|
|
14256
14825
|
);
|
|
14257
14826
|
}
|
|
14258
|
-
if (
|
|
14827
|
+
if (stat2.size > READ_IMAGE_RAW_MAX_BYTES) {
|
|
14259
14828
|
return errorResult(
|
|
14260
|
-
`Image file is ${
|
|
14829
|
+
`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
14830
|
);
|
|
14262
14831
|
}
|
|
14263
14832
|
let buf;
|
|
@@ -14272,10 +14841,10 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14272
14841
|
const probed = probeImage(buf);
|
|
14273
14842
|
if (probed === null) {
|
|
14274
14843
|
return errorResult(
|
|
14275
|
-
`File has an image extension but its content is not a readable PNG/JPEG/GIF/WebP image (bad or truncated header): ${
|
|
14844
|
+
`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
14845
|
);
|
|
14277
14846
|
}
|
|
14278
|
-
registerFileRead(ctx, normalizeFileKey(resolved),
|
|
14847
|
+
registerFileRead(ctx, normalizeFileKey(resolved), stat2.mtimeMs);
|
|
14279
14848
|
const data = {
|
|
14280
14849
|
path: resolved,
|
|
14281
14850
|
mime: probed.mime,
|
|
@@ -14283,7 +14852,7 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
|
|
|
14283
14852
|
height: probed.height,
|
|
14284
14853
|
bytes: buf.length
|
|
14285
14854
|
};
|
|
14286
|
-
const meta = `Image file: ${
|
|
14855
|
+
const meta = `Image file: ${path8.basename(resolved)}
|
|
14287
14856
|
Format: ${probed.mime}
|
|
14288
14857
|
Dimensions: ${probed.width}x${probed.height} px
|
|
14289
14858
|
Size: ${buf.length} bytes`;
|
|
@@ -14306,8 +14875,8 @@ function createReadTool(options = {}) {
|
|
|
14306
14875
|
isConcurrencySafe: true,
|
|
14307
14876
|
touchedPathsOf(args) {
|
|
14308
14877
|
const parsed = ReadArgsSchema.safeParse(args);
|
|
14309
|
-
if (!parsed.success || !
|
|
14310
|
-
return [
|
|
14878
|
+
if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
|
|
14879
|
+
return [path8.resolve(parsed.data.file_path)];
|
|
14311
14880
|
},
|
|
14312
14881
|
async execute(args, ctx) {
|
|
14313
14882
|
if (ctx.signal.aborted) {
|
|
@@ -14321,15 +14890,15 @@ function createReadTool(options = {}) {
|
|
|
14321
14890
|
if (invalid) {
|
|
14322
14891
|
return invalid;
|
|
14323
14892
|
}
|
|
14324
|
-
const resolved =
|
|
14893
|
+
const resolved = path8.resolve(args.file_path);
|
|
14325
14894
|
const fs3 = fsOf(ctx);
|
|
14326
14895
|
const realTarget = await checkRealTarget("Read", resolved, { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
14327
14896
|
if (!realTarget.ok) {
|
|
14328
14897
|
return errorResult(realTarget.reason);
|
|
14329
14898
|
}
|
|
14330
|
-
let
|
|
14899
|
+
let stat2;
|
|
14331
14900
|
try {
|
|
14332
|
-
|
|
14901
|
+
stat2 = await fs3.stat(resolved);
|
|
14333
14902
|
} catch (err) {
|
|
14334
14903
|
if (fsErrorCode(err) === "ENOENT") {
|
|
14335
14904
|
const similar = await findSimilarFiles(fs3, resolved);
|
|
@@ -14338,11 +14907,11 @@ function createReadTool(options = {}) {
|
|
|
14338
14907
|
}
|
|
14339
14908
|
return errorResult(`Failed to read file: ${errorMessageOf(err)}`);
|
|
14340
14909
|
}
|
|
14341
|
-
if (
|
|
14910
|
+
if (stat2.isDirectory()) {
|
|
14342
14911
|
return errorResult(`Path is a directory, not a file: ${resolved}.`);
|
|
14343
14912
|
}
|
|
14344
|
-
if (isImageFileExtension(
|
|
14345
|
-
return executeImageRead(resolved, args, ctx, fs3,
|
|
14913
|
+
if (isImageFileExtension(path8.extname(resolved))) {
|
|
14914
|
+
return executeImageRead(resolved, args, ctx, fs3, stat2, options);
|
|
14346
14915
|
}
|
|
14347
14916
|
const fileKey = normalizeFileKey(resolved);
|
|
14348
14917
|
const effectiveOffset = args.offset ?? 1;
|
|
@@ -14350,7 +14919,7 @@ function createReadTool(options = {}) {
|
|
|
14350
14919
|
const dupWindow = takeDuplicateReadWindow(
|
|
14351
14920
|
ctx,
|
|
14352
14921
|
fileKey,
|
|
14353
|
-
|
|
14922
|
+
stat2.mtimeMs,
|
|
14354
14923
|
effectiveOffset,
|
|
14355
14924
|
effectiveLimit
|
|
14356
14925
|
);
|
|
@@ -14367,9 +14936,9 @@ function createReadTool(options = {}) {
|
|
|
14367
14936
|
data2
|
|
14368
14937
|
);
|
|
14369
14938
|
}
|
|
14370
|
-
if (
|
|
14939
|
+
if (stat2.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
|
|
14371
14940
|
return errorResult(
|
|
14372
|
-
`File is ${
|
|
14941
|
+
`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
14942
|
);
|
|
14374
14943
|
}
|
|
14375
14944
|
let buf;
|
|
@@ -14388,7 +14957,7 @@ function createReadTool(options = {}) {
|
|
|
14388
14957
|
);
|
|
14389
14958
|
}
|
|
14390
14959
|
if (decoded.text.length === 0) {
|
|
14391
|
-
registerFileRead(ctx, fileKey,
|
|
14960
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14392
14961
|
offset: effectiveOffset,
|
|
14393
14962
|
limit: effectiveLimit,
|
|
14394
14963
|
totalLines: 0,
|
|
@@ -14402,7 +14971,7 @@ function createReadTool(options = {}) {
|
|
|
14402
14971
|
const startLine = effectiveOffset;
|
|
14403
14972
|
const maxLines = effectiveLimit;
|
|
14404
14973
|
if (startLine > totalLines) {
|
|
14405
|
-
registerFileRead(ctx, fileKey,
|
|
14974
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14406
14975
|
offset: effectiveOffset,
|
|
14407
14976
|
limit: effectiveLimit,
|
|
14408
14977
|
totalLines,
|
|
@@ -14421,7 +14990,7 @@ function createReadTool(options = {}) {
|
|
|
14421
14990
|
text += `
|
|
14422
14991
|
… (showing lines ${startLine}-${endLine} of ${totalLines}; use offset=${endLine + 1} to continue)`;
|
|
14423
14992
|
}
|
|
14424
|
-
registerFileRead(ctx, fileKey,
|
|
14993
|
+
registerFileRead(ctx, fileKey, stat2.mtimeMs, {
|
|
14425
14994
|
offset: effectiveOffset,
|
|
14426
14995
|
limit: effectiveLimit,
|
|
14427
14996
|
totalLines,
|
|
@@ -14435,7 +15004,7 @@ function createReadTool(options = {}) {
|
|
|
14435
15004
|
var ReadTool = createReadTool();
|
|
14436
15005
|
|
|
14437
15006
|
// ../kernel/src/tools/files/write.ts
|
|
14438
|
-
import
|
|
15007
|
+
import path9 from "node:path";
|
|
14439
15008
|
import { z as z16 } from "zod";
|
|
14440
15009
|
var WriteArgsSchema = z16.object({
|
|
14441
15010
|
file_path: z16.string().describe("Absolute path to the file to write. Relative paths are rejected."),
|
|
@@ -14454,8 +15023,8 @@ var WriteTool = {
|
|
|
14454
15023
|
isConcurrencySafe: false,
|
|
14455
15024
|
mutatedPathsOf(args) {
|
|
14456
15025
|
const parsed = WriteArgsSchema.safeParse(args);
|
|
14457
|
-
if (!parsed.success || !
|
|
14458
|
-
return [
|
|
15026
|
+
if (!parsed.success || !path9.isAbsolute(parsed.data.file_path)) return [];
|
|
15027
|
+
return [path9.resolve(parsed.data.file_path)];
|
|
14459
15028
|
},
|
|
14460
15029
|
async execute(args, ctx) {
|
|
14461
15030
|
if (ctx.signal.aborted) {
|
|
@@ -14469,7 +15038,7 @@ var WriteTool = {
|
|
|
14469
15038
|
if (invalid) {
|
|
14470
15039
|
return invalid;
|
|
14471
15040
|
}
|
|
14472
|
-
const resolved =
|
|
15041
|
+
const resolved = path9.resolve(args.file_path);
|
|
14473
15042
|
const key2 = normalizeFileKey(resolved);
|
|
14474
15043
|
const fs3 = fsOf(ctx);
|
|
14475
15044
|
const realTarget = await checkRealTarget("Write", resolved, {
|
|
@@ -14511,7 +15080,7 @@ var WriteTool = {
|
|
|
14511
15080
|
}
|
|
14512
15081
|
} else {
|
|
14513
15082
|
try {
|
|
14514
|
-
await fs3.mkdir(
|
|
15083
|
+
await fs3.mkdir(path9.dirname(resolved), { recursive: true });
|
|
14515
15084
|
} catch (err) {
|
|
14516
15085
|
return errorResult(`Failed to create parent directories: ${errorMessageOf(err)}`);
|
|
14517
15086
|
}
|
|
@@ -14567,7 +15136,7 @@ ${memoryNearLimitNote(health)}`, data);
|
|
|
14567
15136
|
};
|
|
14568
15137
|
|
|
14569
15138
|
// ../kernel/src/tools/files/edit.ts
|
|
14570
|
-
import
|
|
15139
|
+
import path10 from "node:path";
|
|
14571
15140
|
import { z as z17 } from "zod";
|
|
14572
15141
|
var EditArgsSchema = z17.object({
|
|
14573
15142
|
file_path: z17.string().describe("Absolute path to the file to edit. Relative paths are rejected."),
|
|
@@ -14610,8 +15179,8 @@ var EditTool = {
|
|
|
14610
15179
|
isConcurrencySafe: false,
|
|
14611
15180
|
mutatedPathsOf(args) {
|
|
14612
15181
|
const parsed = EditArgsSchema.safeParse(args);
|
|
14613
|
-
if (!parsed.success || !
|
|
14614
|
-
return [
|
|
15182
|
+
if (!parsed.success || !path10.isAbsolute(parsed.data.file_path)) return [];
|
|
15183
|
+
return [path10.resolve(parsed.data.file_path)];
|
|
14615
15184
|
},
|
|
14616
15185
|
async execute(args, ctx) {
|
|
14617
15186
|
if (ctx.signal.aborted) {
|
|
@@ -14625,7 +15194,7 @@ var EditTool = {
|
|
|
14625
15194
|
if (invalid) {
|
|
14626
15195
|
return invalid;
|
|
14627
15196
|
}
|
|
14628
|
-
const resolved =
|
|
15197
|
+
const resolved = path10.resolve(args.file_path);
|
|
14629
15198
|
const key2 = normalizeFileKey(resolved);
|
|
14630
15199
|
const fs3 = fsOf(ctx);
|
|
14631
15200
|
const realTarget = await checkRealTarget("Edit", resolved, {
|
|
@@ -14636,19 +15205,19 @@ var EditTool = {
|
|
|
14636
15205
|
if (!realTarget.ok) {
|
|
14637
15206
|
return errorResult(realTarget.reason);
|
|
14638
15207
|
}
|
|
14639
|
-
let
|
|
15208
|
+
let stat2;
|
|
14640
15209
|
try {
|
|
14641
|
-
|
|
15210
|
+
stat2 = await fs3.stat(resolved);
|
|
14642
15211
|
} catch (err) {
|
|
14643
15212
|
if (fsErrorCode(err) === "ENOENT") {
|
|
14644
15213
|
return errorResult(`File does not exist: ${resolved}. Use the Write tool to create a new file.`);
|
|
14645
15214
|
}
|
|
14646
15215
|
return errorResult(`Failed to access file: ${errorMessageOf(err)}`);
|
|
14647
15216
|
}
|
|
14648
|
-
if (
|
|
15217
|
+
if (stat2.isDirectory()) {
|
|
14649
15218
|
return errorResult(`Path is a directory, not a file: ${resolved}.`);
|
|
14650
15219
|
}
|
|
14651
|
-
const guard = checkStaleWriteGuard(ctx, key2,
|
|
15220
|
+
const guard = checkStaleWriteGuard(ctx, key2, stat2.mtimeMs, "editing");
|
|
14652
15221
|
if (guard) {
|
|
14653
15222
|
return guard;
|
|
14654
15223
|
}
|
|
@@ -14753,7 +15322,7 @@ ${memoryNearLimitNote(health)}`, data);
|
|
|
14753
15322
|
|
|
14754
15323
|
// ../kernel/src/tools/search/glob-tool.ts
|
|
14755
15324
|
import { z as z18 } from "zod";
|
|
14756
|
-
import
|
|
15325
|
+
import path11 from "node:path";
|
|
14757
15326
|
|
|
14758
15327
|
// ../kernel/src/tools/search/walker.ts
|
|
14759
15328
|
var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -14847,7 +15416,7 @@ var globTool = {
|
|
|
14847
15416
|
return errorResult3(`path does not exist: ${root}`);
|
|
14848
15417
|
}
|
|
14849
15418
|
if (!rootStat.isDirectory()) return errorResult3(`path is not a directory: ${root}`);
|
|
14850
|
-
const realRoot = await checkRealTarget("Glob",
|
|
15419
|
+
const realRoot = await checkRealTarget("Glob", path11.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
14851
15420
|
if (!realRoot.ok) return errorResult3(realRoot.reason);
|
|
14852
15421
|
let matcher;
|
|
14853
15422
|
try {
|
|
@@ -14886,7 +15455,7 @@ var globTool = {
|
|
|
14886
15455
|
|
|
14887
15456
|
// ../kernel/src/tools/search/grep-tool.ts
|
|
14888
15457
|
import { z as z19 } from "zod";
|
|
14889
|
-
import
|
|
15458
|
+
import path12 from "node:path";
|
|
14890
15459
|
|
|
14891
15460
|
// ../kernel/src/tools/search/js-engine.ts
|
|
14892
15461
|
var GREP_MAX_FILE_SIZE = 4 * 1024 * 1024;
|
|
@@ -15360,7 +15929,7 @@ var grepTool = {
|
|
|
15360
15929
|
return finish(errorResult4(`path does not exist: ${root}`));
|
|
15361
15930
|
}
|
|
15362
15931
|
if (!rootStat.isDirectory()) return finish(errorResult4(`path is not a directory: ${root}`));
|
|
15363
|
-
const realRoot = await checkRealTarget("Grep",
|
|
15932
|
+
const realRoot = await checkRealTarget("Grep", path12.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
15364
15933
|
if (!realRoot.ok) return finish(errorResult4(realRoot.reason));
|
|
15365
15934
|
try {
|
|
15366
15935
|
new RegExp(args.pattern, args.case_insensitive ? "i" : "");
|
|
@@ -15445,7 +16014,7 @@ var grepTool = {
|
|
|
15445
16014
|
|
|
15446
16015
|
// ../kernel/src/tools/search/list-tool.ts
|
|
15447
16016
|
import { z as z20 } from "zod";
|
|
15448
|
-
import
|
|
16017
|
+
import path13 from "node:path";
|
|
15449
16018
|
var LIST_MAX_ENTRIES = 500;
|
|
15450
16019
|
var ListArgsSchema = z20.object({
|
|
15451
16020
|
path: z20.string().min(1),
|
|
@@ -15475,7 +16044,7 @@ var listTool = {
|
|
|
15475
16044
|
touchedPathsOf(args) {
|
|
15476
16045
|
const parsed = ListArgsSchema.safeParse(args);
|
|
15477
16046
|
if (!parsed.success || !isAbsolutePath(parsed.data.path)) return [];
|
|
15478
|
-
return [
|
|
16047
|
+
return [path13.resolve(parsed.data.path)];
|
|
15479
16048
|
},
|
|
15480
16049
|
async execute(args, ctx) {
|
|
15481
16050
|
if (ctx.signal.aborted) return errorResult5("aborted");
|
|
@@ -15489,7 +16058,7 @@ var listTool = {
|
|
|
15489
16058
|
return errorResult5(`path does not exist: ${root}`);
|
|
15490
16059
|
}
|
|
15491
16060
|
if (!rootStat.isDirectory()) return errorResult5(`path is not a directory: ${root}`);
|
|
15492
|
-
const realRoot = await checkRealTarget("List",
|
|
16061
|
+
const realRoot = await checkRealTarget("List", path13.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
|
|
15493
16062
|
if (!realRoot.ok) return errorResult5(realRoot.reason);
|
|
15494
16063
|
let ignoreMatchers = [];
|
|
15495
16064
|
try {
|
|
@@ -16020,7 +16589,7 @@ ${tail}`;
|
|
|
16020
16589
|
|
|
16021
16590
|
// ../kernel/src/tools/shell/output-file.ts
|
|
16022
16591
|
import { createWriteStream, mkdirSync } from "node:fs";
|
|
16023
|
-
import * as
|
|
16592
|
+
import * as path14 from "node:path";
|
|
16024
16593
|
var SHELL_OUTPUT_SPILL_THRESHOLD_CHARS = 5e4;
|
|
16025
16594
|
var SHELL_OUTPUT_DIR_NAME = "shell-output";
|
|
16026
16595
|
function sanitizeFileStem(stem) {
|
|
@@ -16029,7 +16598,7 @@ function sanitizeFileStem(stem) {
|
|
|
16029
16598
|
}
|
|
16030
16599
|
function shellOutputFilePath(sessionCwd, toolCallId) {
|
|
16031
16600
|
const stem = toolCallId !== void 0 && toolCallId.trim().length > 0 ? sanitizeFileStem(toolCallId) : `shell-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
16032
|
-
return
|
|
16601
|
+
return path14.join(sessionCwd, ".tansr", SHELL_OUTPUT_DIR_NAME, `${stem}.log`);
|
|
16033
16602
|
}
|
|
16034
16603
|
var ShellOutputSink = class {
|
|
16035
16604
|
filePath;
|
|
@@ -16065,7 +16634,7 @@ var ShellOutputSink = class {
|
|
|
16065
16634
|
forceOpen() {
|
|
16066
16635
|
if (this.#stream !== void 0 || this.#writeError !== void 0 || this.#closed) return;
|
|
16067
16636
|
try {
|
|
16068
|
-
mkdirSync(
|
|
16637
|
+
mkdirSync(path14.dirname(this.filePath), { recursive: true });
|
|
16069
16638
|
const stream = createWriteStream(this.filePath, { encoding: "utf8" });
|
|
16070
16639
|
stream.on("error", (err) => {
|
|
16071
16640
|
this.#writeError = err.message;
|
|
@@ -17556,104 +18125,7 @@ function scrubSecrets(text, secrets) {
|
|
|
17556
18125
|
}
|
|
17557
18126
|
|
|
17558
18127
|
// ../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
18128
|
var WEBSEARCH_RESPONSE_MAX_BYTES = 512 * 1024;
|
|
17562
|
-
var WEBSEARCH_PROVIDER_TIMEOUT_MS = 15e3;
|
|
17563
|
-
var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
|
|
17564
|
-
function safeEndpointLabel(endpoint) {
|
|
17565
|
-
try {
|
|
17566
|
-
const u = new URL(endpoint);
|
|
17567
|
-
return `${u.origin}${u.pathname}`;
|
|
17568
|
-
} catch {
|
|
17569
|
-
return "(invalid endpoint URL)";
|
|
17570
|
-
}
|
|
17571
|
-
}
|
|
17572
|
-
function pickItems(payload) {
|
|
17573
|
-
if (typeof payload !== "object" || payload === null) return [];
|
|
17574
|
-
const results = payload.results;
|
|
17575
|
-
if (!Array.isArray(results)) return [];
|
|
17576
|
-
return results.filter(
|
|
17577
|
-
(item) => typeof item === "object" && item !== null
|
|
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
18129
|
|
|
17658
18130
|
// ../kernel/src/tools/web/search-tool.ts
|
|
17659
18131
|
import { z as z27 } from "zod";
|
|
@@ -18591,7 +19063,7 @@ function applyToolOverrides(filtered, overrides) {
|
|
|
18591
19063
|
}
|
|
18592
19064
|
|
|
18593
19065
|
// ../kernel/src/agents/subagent-memory-fence.ts
|
|
18594
|
-
import
|
|
19066
|
+
import path15 from "node:path";
|
|
18595
19067
|
function subagentMemoryFenceOf(ctx) {
|
|
18596
19068
|
if (ctx === void 0 || ctx.dirs.length === 0) return void 0;
|
|
18597
19069
|
return {
|
|
@@ -18606,7 +19078,7 @@ function writeTargetOf(args, cwd) {
|
|
|
18606
19078
|
if (args === null || typeof args !== "object") return null;
|
|
18607
19079
|
const raw = args.file_path;
|
|
18608
19080
|
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
18609
|
-
return
|
|
19081
|
+
return path15.isAbsolute(raw) ? path15.resolve(raw) : path15.resolve(cwd, raw);
|
|
18610
19082
|
}
|
|
18611
19083
|
function fencedSubagentWriteTool(tool, fence) {
|
|
18612
19084
|
return {
|
|
@@ -18914,10 +19386,10 @@ async function runSubagent(options) {
|
|
|
18914
19386
|
}
|
|
18915
19387
|
|
|
18916
19388
|
// ../kernel/src/tools/task/background.ts
|
|
18917
|
-
import * as
|
|
19389
|
+
import * as path16 from "node:path";
|
|
18918
19390
|
var TASK_AGENT_OUTPUT_DIR_NAME = "agent-output";
|
|
18919
19391
|
function taskAgentOutputFilePath(sessionCwd, agentId) {
|
|
18920
|
-
return
|
|
19392
|
+
return path16.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
|
|
18921
19393
|
}
|
|
18922
19394
|
function taskSettlementStopReason(reason, aborted) {
|
|
18923
19395
|
if (aborted) return "aborted";
|
|
@@ -20167,7 +20639,7 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
20167
20639
|
|
|
20168
20640
|
// ../kernel/src/tools/mcp/win32-spawn.ts
|
|
20169
20641
|
import fs2 from "node:fs";
|
|
20170
|
-
import
|
|
20642
|
+
import path17 from "node:path";
|
|
20171
20643
|
var CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
20172
20644
|
var SPAWNABLE_EXTS = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
20173
20645
|
function escapeCmdCommand(command) {
|
|
@@ -20206,7 +20678,7 @@ function defaultFileExists(filePath) {
|
|
|
20206
20678
|
}
|
|
20207
20679
|
}
|
|
20208
20680
|
function resolveCommandFile(command, env, cwd, fileExists) {
|
|
20209
|
-
const w =
|
|
20681
|
+
const w = path17.win32;
|
|
20210
20682
|
const exts = spawnableExts(envLookup(env, "PATHEXT"));
|
|
20211
20683
|
const bases = [];
|
|
20212
20684
|
if (command.includes("/") || command.includes("\\")) {
|
|
@@ -20240,7 +20712,7 @@ function wrapWithComSpec(target, args, env) {
|
|
|
20240
20712
|
function planStdioSpawn(command, args, options) {
|
|
20241
20713
|
const platform = options.platform ?? process.platform;
|
|
20242
20714
|
if (platform !== "win32") return { file: command, args };
|
|
20243
|
-
const ext =
|
|
20715
|
+
const ext = path17.win32.extname(command).toUpperCase();
|
|
20244
20716
|
if (ext === ".EXE" || ext === ".COM") return { file: command, args };
|
|
20245
20717
|
if (ext === ".CMD" || ext === ".BAT") return wrapWithComSpec(command, args, options.env);
|
|
20246
20718
|
if (ext !== "") return { file: command, args };
|
|
@@ -20248,7 +20720,7 @@ function planStdioSpawn(command, args, options) {
|
|
|
20248
20720
|
const fileExists = options.fileExists ?? defaultFileExists;
|
|
20249
20721
|
const resolved = resolveCommandFile(command, options.env, cwd, fileExists);
|
|
20250
20722
|
if (resolved === void 0) return { file: command, args };
|
|
20251
|
-
const resolvedExt =
|
|
20723
|
+
const resolvedExt = path17.win32.extname(resolved).toUpperCase();
|
|
20252
20724
|
if (resolvedExt === ".CMD" || resolvedExt === ".BAT") {
|
|
20253
20725
|
return wrapWithComSpec(resolved, args, options.env);
|
|
20254
20726
|
}
|
|
@@ -20378,11 +20850,11 @@ function buildMcpCatalogSegment(servers) {
|
|
|
20378
20850
|
if (text.length === 0) return null;
|
|
20379
20851
|
return { text, cacheable: true, label: MCP_CATALOG_LABEL };
|
|
20380
20852
|
}
|
|
20381
|
-
function
|
|
20853
|
+
function isRecord2(value) {
|
|
20382
20854
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20383
20855
|
}
|
|
20384
20856
|
function describeSchemaType(prop) {
|
|
20385
|
-
if (!
|
|
20857
|
+
if (!isRecord2(prop)) return "any";
|
|
20386
20858
|
if (Array.isArray(prop["enum"])) return "enum";
|
|
20387
20859
|
const type = prop["type"];
|
|
20388
20860
|
if (typeof type === "string") return type;
|
|
@@ -20393,7 +20865,7 @@ function describeSchemaType(prop) {
|
|
|
20393
20865
|
return "any";
|
|
20394
20866
|
}
|
|
20395
20867
|
function summarizeMcpParams(inputSchema) {
|
|
20396
|
-
const properties =
|
|
20868
|
+
const properties = isRecord2(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
|
|
20397
20869
|
if (properties === void 0 || Object.keys(properties).length === 0) {
|
|
20398
20870
|
return "(parameters unspecified)";
|
|
20399
20871
|
}
|
|
@@ -20549,14 +21021,14 @@ var SseParser = class {
|
|
|
20549
21021
|
var JSONRPC_VERSION = "2.0";
|
|
20550
21022
|
var JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
20551
21023
|
var JSONRPC_INTERNAL_ERROR = -32603;
|
|
20552
|
-
function
|
|
21024
|
+
function isRecord3(value) {
|
|
20553
21025
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20554
21026
|
}
|
|
20555
21027
|
function isValidId(value) {
|
|
20556
21028
|
return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
20557
21029
|
}
|
|
20558
21030
|
function classifyJsonRpcValue(value) {
|
|
20559
|
-
if (!
|
|
21031
|
+
if (!isRecord3(value)) return { ok: false, error: "invalid_message" };
|
|
20560
21032
|
if (value["jsonrpc"] !== JSONRPC_VERSION) return { ok: false, error: "invalid_message" };
|
|
20561
21033
|
const method = value["method"];
|
|
20562
21034
|
if (typeof method === "string" && method.length > 0) {
|
|
@@ -20569,7 +21041,7 @@ function classifyJsonRpcValue(value) {
|
|
|
20569
21041
|
if ("error" in value) {
|
|
20570
21042
|
const err = value["error"];
|
|
20571
21043
|
const idOk = value["id"] === null || isValidId(value["id"]);
|
|
20572
|
-
if (!idOk || !
|
|
21044
|
+
if (!idOk || !isRecord3(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
|
|
20573
21045
|
return { ok: false, error: "invalid_message" };
|
|
20574
21046
|
}
|
|
20575
21047
|
return { ok: true, kind: "error", message: value };
|
|
@@ -20612,14 +21084,14 @@ function buildErrorResponse(id, code, message, data) {
|
|
|
20612
21084
|
// ../kernel/src/tools/mcp/stream-connection.ts
|
|
20613
21085
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
20614
21086
|
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
20615
|
-
function
|
|
21087
|
+
function isRecord4(value) {
|
|
20616
21088
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20617
21089
|
}
|
|
20618
21090
|
function createDefaultServerRequestHandler(serverName, onEvent) {
|
|
20619
21091
|
return (method, params) => {
|
|
20620
21092
|
if (method === "ping") return Promise.resolve({ result: {} });
|
|
20621
21093
|
if (method === "elicitation/create") {
|
|
20622
|
-
const p =
|
|
21094
|
+
const p = isRecord4(params) ? params : {};
|
|
20623
21095
|
const mode = p["mode"] === "url" ? "url" : "form";
|
|
20624
21096
|
const message = typeof p["message"] === "string" ? p["message"].slice(0, 200) : void 0;
|
|
20625
21097
|
onEvent?.({
|
|
@@ -20868,7 +21340,7 @@ var StreamMcpConnection = class {
|
|
|
20868
21340
|
}
|
|
20869
21341
|
#handleServerNotification(notification) {
|
|
20870
21342
|
if (notification.method === "notifications/message") {
|
|
20871
|
-
const p =
|
|
21343
|
+
const p = isRecord4(notification.params) ? notification.params : {};
|
|
20872
21344
|
const level = typeof p["level"] === "string" ? p["level"] : "info";
|
|
20873
21345
|
const data = p["data"];
|
|
20874
21346
|
const message = typeof data === "string" ? data : data !== void 0 ? JSON.stringify(data) : "";
|
|
@@ -20882,7 +21354,7 @@ var StreamMcpConnection = class {
|
|
|
20882
21354
|
}
|
|
20883
21355
|
};
|
|
20884
21356
|
function validateInitializeResult(serverName, result) {
|
|
20885
|
-
if (!
|
|
21357
|
+
if (!isRecord4(result)) {
|
|
20886
21358
|
throw new McpError("handshake_failed", "MCP initialize returned a non-object result.", {
|
|
20887
21359
|
server: serverName
|
|
20888
21360
|
});
|
|
@@ -20907,8 +21379,8 @@ function validateInitializeResult(serverName, result) {
|
|
|
20907
21379
|
);
|
|
20908
21380
|
}
|
|
20909
21381
|
const rawInfo = result["serverInfo"];
|
|
20910
|
-
const serverInfo =
|
|
20911
|
-
const capabilities =
|
|
21382
|
+
const serverInfo = isRecord4(rawInfo) && typeof rawInfo["name"] === "string" && typeof rawInfo["version"] === "string" ? { name: rawInfo["name"], version: rawInfo["version"] } : void 0;
|
|
21383
|
+
const capabilities = isRecord4(result["capabilities"]) ? result["capabilities"] : {};
|
|
20912
21384
|
const rawInstructions = result["instructions"];
|
|
20913
21385
|
const instructions = typeof rawInstructions === "string" && rawInstructions.trim().length > 0 ? capMcpInstructions(rawInstructions).text : void 0;
|
|
20914
21386
|
return {
|
|
@@ -21112,7 +21584,7 @@ ${tail}`, {
|
|
|
21112
21584
|
}
|
|
21113
21585
|
|
|
21114
21586
|
// ../kernel/src/tools/mcp/http-transport.ts
|
|
21115
|
-
var
|
|
21587
|
+
var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
|
|
21116
21588
|
var SESSION_HEADER = "mcp-session-id";
|
|
21117
21589
|
var PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
21118
21590
|
var CANCEL_NOTIFY_TIMEOUT_MS = 3e3;
|
|
@@ -21139,7 +21611,7 @@ var HttpMcpConnection = class {
|
|
|
21139
21611
|
this.serverName = options.serverName;
|
|
21140
21612
|
this.#url = config.url;
|
|
21141
21613
|
this.#configHeaders = config.headers ?? {};
|
|
21142
|
-
this.#fetchImpl = options.fetchImpl ??
|
|
21614
|
+
this.#fetchImpl = options.fetchImpl ?? defaultFetch2;
|
|
21143
21615
|
this.#onEvent = options.onEvent;
|
|
21144
21616
|
this.#defaultRequestTimeoutMs = options.defaultRequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
21145
21617
|
this.#serverRequestHandler = options.onServerRequest ?? createDefaultServerRequestHandler(options.serverName, options.onEvent);
|
|
@@ -21504,7 +21976,7 @@ async function connectHttpMcpServer(config, options) {
|
|
|
21504
21976
|
}
|
|
21505
21977
|
|
|
21506
21978
|
// ../kernel/src/tools/mcp/bridge.ts
|
|
21507
|
-
import { createHash as
|
|
21979
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
21508
21980
|
import { z as z33 } from "zod";
|
|
21509
21981
|
var MCP_TOOL_NAME_PREFIX = "mcp__";
|
|
21510
21982
|
var MCP_REMOTE_TOOL_NAME_RE = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
@@ -21512,7 +21984,7 @@ var MAX_LIST_PAGES = 64;
|
|
|
21512
21984
|
function buildMcpToolName(serverName, toolName2) {
|
|
21513
21985
|
const full = `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName2}`;
|
|
21514
21986
|
if (full.length <= MCP_BRIDGED_TOOL_NAME_MAX_CHARS) return full;
|
|
21515
|
-
const hash =
|
|
21987
|
+
const hash = createHash7("sha256").update(full, "utf8").digest("hex").slice(0, MCP_NAME_FOLD_HASH_CHARS);
|
|
21516
21988
|
const prefix = `${MCP_TOOL_NAME_PREFIX}${serverName}__`;
|
|
21517
21989
|
const tailBudget = MCP_BRIDGED_TOOL_NAME_MAX_CHARS - prefix.length;
|
|
21518
21990
|
if (tailBudget >= MCP_NAME_FOLD_HASH_CHARS + 1) {
|
|
@@ -21522,7 +21994,7 @@ function buildMcpToolName(serverName, toolName2) {
|
|
|
21522
21994
|
const head = full.slice(0, MCP_BRIDGED_TOOL_NAME_MAX_CHARS - MCP_NAME_FOLD_HASH_CHARS - 1);
|
|
21523
21995
|
return `${head}_${hash}`;
|
|
21524
21996
|
}
|
|
21525
|
-
function
|
|
21997
|
+
function isRecord5(value) {
|
|
21526
21998
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21527
21999
|
}
|
|
21528
22000
|
async function discoverMcpTools(connection, opts = {}) {
|
|
@@ -21536,9 +22008,9 @@ async function discoverMcpTools(connection, opts = {}) {
|
|
|
21536
22008
|
cursor !== void 0 ? { cursor } : {},
|
|
21537
22009
|
opts
|
|
21538
22010
|
);
|
|
21539
|
-
if (!
|
|
22011
|
+
if (!isRecord5(result) || !Array.isArray(result["tools"])) break;
|
|
21540
22012
|
for (const raw of result["tools"]) {
|
|
21541
|
-
if (!
|
|
22013
|
+
if (!isRecord5(raw) || typeof raw["name"] !== "string") continue;
|
|
21542
22014
|
const name = raw["name"];
|
|
21543
22015
|
if (!MCP_REMOTE_TOOL_NAME_RE.test(name)) {
|
|
21544
22016
|
invalidNames.push(name.slice(0, 160));
|
|
@@ -21549,7 +22021,7 @@ async function discoverMcpTools(connection, opts = {}) {
|
|
|
21549
22021
|
tools.push({
|
|
21550
22022
|
name,
|
|
21551
22023
|
description: typeof raw["description"] === "string" ? raw["description"] : "",
|
|
21552
|
-
inputSchema:
|
|
22024
|
+
inputSchema: isRecord5(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
|
|
21553
22025
|
});
|
|
21554
22026
|
}
|
|
21555
22027
|
const next = result["nextCursor"];
|
|
@@ -21572,7 +22044,7 @@ function buildLooseArgsSchema(inputSchema) {
|
|
|
21572
22044
|
});
|
|
21573
22045
|
}
|
|
21574
22046
|
function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
21575
|
-
if (!
|
|
22047
|
+
if (!isRecord5(raw)) {
|
|
21576
22048
|
return {
|
|
21577
22049
|
content: [{ t: "text", text: "MCP server returned a malformed tools/call result." }],
|
|
21578
22050
|
isError: true,
|
|
@@ -21586,11 +22058,11 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
|
21586
22058
|
};
|
|
21587
22059
|
}
|
|
21588
22060
|
const isError = raw["isError"] === true;
|
|
21589
|
-
const structuredContent =
|
|
22061
|
+
const structuredContent = isRecord5(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
|
|
21590
22062
|
const blocks = Array.isArray(raw["content"]) ? raw["content"] : [];
|
|
21591
22063
|
const content = [];
|
|
21592
22064
|
for (const block of blocks) {
|
|
21593
|
-
if (!
|
|
22065
|
+
if (!isRecord5(block)) continue;
|
|
21594
22066
|
const type = block["type"];
|
|
21595
22067
|
if (type === "text" && typeof block["text"] === "string") {
|
|
21596
22068
|
content.push({ t: "text", text: block["text"] });
|
|
@@ -21612,7 +22084,7 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
|
|
|
21612
22084
|
content.push({ t: "text", text: `[resource link]${name} ${block["uri"]}${description}` });
|
|
21613
22085
|
continue;
|
|
21614
22086
|
}
|
|
21615
|
-
if (type === "resource" &&
|
|
22087
|
+
if (type === "resource" && isRecord5(block["resource"])) {
|
|
21616
22088
|
const resource = block["resource"];
|
|
21617
22089
|
const uri = typeof resource["uri"] === "string" ? resource["uri"] : "(unknown uri)";
|
|
21618
22090
|
if (typeof resource["text"] === "string") {
|
|
@@ -21893,7 +22365,7 @@ function extractMcpServersSection(layersDescending, addDiagnostic) {
|
|
|
21893
22365
|
}
|
|
21894
22366
|
|
|
21895
22367
|
// ../kernel/src/tools/mcp/catalog-store.ts
|
|
21896
|
-
import { createHash as
|
|
22368
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
21897
22369
|
var MCP_CATALOG_STORE_VERSION = 1;
|
|
21898
22370
|
function stableStringify2(value) {
|
|
21899
22371
|
if (value === null || typeof value !== "object") {
|
|
@@ -21907,10 +22379,10 @@ function stableStringify2(value) {
|
|
|
21907
22379
|
return `{${parts.join(",")}}`;
|
|
21908
22380
|
}
|
|
21909
22381
|
function computeMcpServerConfigFingerprint(config) {
|
|
21910
|
-
return
|
|
22382
|
+
return createHash8("sha256").update(stableStringify2(config), "utf8").digest("hex");
|
|
21911
22383
|
}
|
|
21912
22384
|
function computeMcpToolDefinitionHash(tool) {
|
|
21913
|
-
return
|
|
22385
|
+
return createHash8("sha256").update(
|
|
21914
22386
|
stableStringify2({
|
|
21915
22387
|
name: tool.name,
|
|
21916
22388
|
description: tool.description,
|
|
@@ -21988,14 +22460,14 @@ function resolveEnvRefs(record, baseEnv) {
|
|
|
21988
22460
|
}
|
|
21989
22461
|
return { resolved, missing };
|
|
21990
22462
|
}
|
|
21991
|
-
function
|
|
22463
|
+
function isRecord6(value) {
|
|
21992
22464
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21993
22465
|
}
|
|
21994
22466
|
function createElicitationAwareHandler(serverName, onElicitation, onEvent) {
|
|
21995
22467
|
const fallback = createDefaultServerRequestHandler(serverName, onEvent);
|
|
21996
22468
|
return async (method, params) => {
|
|
21997
22469
|
if (method !== "elicitation/create") return fallback(method, params);
|
|
21998
|
-
const p =
|
|
22470
|
+
const p = isRecord6(params) ? params : {};
|
|
21999
22471
|
const mode = p["mode"] === "url" ? "url" : "form";
|
|
22000
22472
|
try {
|
|
22001
22473
|
const response = await onElicitation({
|
|
@@ -22549,10 +23021,10 @@ async function initMcpLazyManager(toolset, servers, options = {}) {
|
|
|
22549
23021
|
}
|
|
22550
23022
|
|
|
22551
23023
|
// ../kernel/src/config/load.ts
|
|
22552
|
-
import
|
|
23024
|
+
import os3 from "node:os";
|
|
22553
23025
|
|
|
22554
23026
|
// ../kernel/src/hooks/config.ts
|
|
22555
|
-
import { createHash as
|
|
23027
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
22556
23028
|
|
|
22557
23029
|
// ../kernel/src/config/merge.ts
|
|
22558
23030
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -22582,12 +23054,12 @@ function deepMergeRawLayers(rawsAscending) {
|
|
|
22582
23054
|
}
|
|
22583
23055
|
return structuredClone(acc);
|
|
22584
23056
|
}
|
|
22585
|
-
function displayPath2(
|
|
22586
|
-
return
|
|
23057
|
+
function displayPath2(path23) {
|
|
23058
|
+
return path23.map(String).join(".");
|
|
22587
23059
|
}
|
|
22588
|
-
function getAtPath(root,
|
|
23060
|
+
function getAtPath(root, path23) {
|
|
22589
23061
|
let cur = root;
|
|
22590
|
-
for (const seg of
|
|
23062
|
+
for (const seg of path23) {
|
|
22591
23063
|
if (typeof seg === "number") {
|
|
22592
23064
|
if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return void 0;
|
|
22593
23065
|
cur = cur[seg];
|
|
@@ -22598,11 +23070,11 @@ function getAtPath(root, path20) {
|
|
|
22598
23070
|
}
|
|
22599
23071
|
return cur;
|
|
22600
23072
|
}
|
|
22601
|
-
function hasPath(root,
|
|
22602
|
-
if (
|
|
23073
|
+
function hasPath(root, path23) {
|
|
23074
|
+
if (path23.length === 0) return true;
|
|
22603
23075
|
let cur = root;
|
|
22604
|
-
for (let i = 0; i <
|
|
22605
|
-
const seg =
|
|
23076
|
+
for (let i = 0; i < path23.length; i++) {
|
|
23077
|
+
const seg = path23[i];
|
|
22606
23078
|
if (typeof seg === "number") {
|
|
22607
23079
|
if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return false;
|
|
22608
23080
|
cur = cur[seg];
|
|
@@ -22613,25 +23085,25 @@ function hasPath(root, path20) {
|
|
|
22613
23085
|
}
|
|
22614
23086
|
return true;
|
|
22615
23087
|
}
|
|
22616
|
-
function sanitizeDropPath(root,
|
|
23088
|
+
function sanitizeDropPath(root, path23) {
|
|
22617
23089
|
let cur = root;
|
|
22618
|
-
for (let i = 0; i <
|
|
22619
|
-
const seg =
|
|
23090
|
+
for (let i = 0; i < path23.length; i++) {
|
|
23091
|
+
const seg = path23[i];
|
|
22620
23092
|
const next = getAtPath(cur, [seg]);
|
|
22621
|
-
if (Array.isArray(next) && i <
|
|
22622
|
-
return
|
|
23093
|
+
if (Array.isArray(next) && i < path23.length - 1) {
|
|
23094
|
+
return path23.slice(0, i + 1);
|
|
22623
23095
|
}
|
|
22624
|
-
if (typeof
|
|
22625
|
-
return
|
|
23096
|
+
if (typeof path23[i + 1] === "number") {
|
|
23097
|
+
return path23.slice(0, i + 1);
|
|
22626
23098
|
}
|
|
22627
23099
|
cur = next;
|
|
22628
23100
|
}
|
|
22629
|
-
return
|
|
23101
|
+
return path23;
|
|
22630
23102
|
}
|
|
22631
|
-
function deletePath(root,
|
|
22632
|
-
if (
|
|
22633
|
-
const parent = getAtPath(root,
|
|
22634
|
-
const leaf =
|
|
23103
|
+
function deletePath(root, path23) {
|
|
23104
|
+
if (path23.length === 0) return;
|
|
23105
|
+
const parent = getAtPath(root, path23.slice(0, -1));
|
|
23106
|
+
const leaf = path23[path23.length - 1];
|
|
22635
23107
|
if (isPlainObject2(parent) && typeof leaf === "string") {
|
|
22636
23108
|
delete parent[leaf];
|
|
22637
23109
|
}
|
|
@@ -22721,16 +23193,16 @@ function canonicalJson(value) {
|
|
|
22721
23193
|
return JSON.stringify(value) ?? "null";
|
|
22722
23194
|
}
|
|
22723
23195
|
function configHash8(dedupeKey) {
|
|
22724
|
-
return
|
|
23196
|
+
return createHash9("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
|
|
22725
23197
|
}
|
|
22726
|
-
function parseExecutor(value, layer,
|
|
23198
|
+
function parseExecutor(value, layer, path23, file, addDiagnostic) {
|
|
22727
23199
|
const fileRef = file !== void 0 ? { file } : {};
|
|
22728
23200
|
const invalid = (params) => {
|
|
22729
23201
|
addDiagnostic({
|
|
22730
23202
|
severity: "error",
|
|
22731
23203
|
code: "hook_invalid_value",
|
|
22732
23204
|
layer,
|
|
22733
|
-
path:
|
|
23205
|
+
path: path23,
|
|
22734
23206
|
...fileRef,
|
|
22735
23207
|
params
|
|
22736
23208
|
});
|
|
@@ -22778,7 +23250,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
|
|
|
22778
23250
|
severity: "error",
|
|
22779
23251
|
code: "hook_url_invalid",
|
|
22780
23252
|
layer,
|
|
22781
|
-
path: `${
|
|
23253
|
+
path: `${path23}.url`,
|
|
22782
23254
|
...fileRef,
|
|
22783
23255
|
params: { reason: "unparsable" }
|
|
22784
23256
|
});
|
|
@@ -22789,7 +23261,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
|
|
|
22789
23261
|
severity: "error",
|
|
22790
23262
|
code: "hook_url_invalid",
|
|
22791
23263
|
layer,
|
|
22792
|
-
path: `${
|
|
23264
|
+
path: `${path23}.url`,
|
|
22793
23265
|
...fileRef,
|
|
22794
23266
|
params: { reason: "unsupported_protocol", protocol: parsed.protocol }
|
|
22795
23267
|
});
|
|
@@ -23380,7 +23852,7 @@ function migrateLayerRaw(raw) {
|
|
|
23380
23852
|
|
|
23381
23853
|
// ../kernel/src/config/paths.ts
|
|
23382
23854
|
import { promises as fsPromises } from "node:fs";
|
|
23383
|
-
import
|
|
23855
|
+
import path18 from "node:path";
|
|
23384
23856
|
var SETTINGS_FILE_NAME = "settings.json";
|
|
23385
23857
|
var LOCAL_SETTINGS_FILE_NAME = "settings.local.json";
|
|
23386
23858
|
var TANSR_DIR_NAME = ".tansr";
|
|
@@ -23407,27 +23879,27 @@ function normalizeForCompare(p, platform) {
|
|
|
23407
23879
|
function defaultManagedPaths(platform, env) {
|
|
23408
23880
|
if (platform === "win32") {
|
|
23409
23881
|
const programData = readTrimmedEnv2(env, "PROGRAMDATA") ?? "C:\\ProgramData";
|
|
23410
|
-
return [
|
|
23882
|
+
return [path18.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
|
|
23411
23883
|
}
|
|
23412
|
-
return [
|
|
23884
|
+
return [path18.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
|
|
23413
23885
|
}
|
|
23414
23886
|
async function discoverLayerFiles(options) {
|
|
23415
23887
|
const { fs: fs3, env, platform } = options;
|
|
23416
23888
|
const configDirOverride = readTrimmedEnv2(env, "TANSR_CONFIG_DIR");
|
|
23417
|
-
const userDir = configDirOverride ??
|
|
23418
|
-
const userFile =
|
|
23419
|
-
const homeKey = normalizeForCompare(
|
|
23889
|
+
const userDir = configDirOverride ?? path18.join(options.homedir, TANSR_DIR_NAME);
|
|
23890
|
+
const userFile = path18.join(userDir, SETTINGS_FILE_NAME);
|
|
23891
|
+
const homeKey = normalizeForCompare(path18.resolve(options.homedir), platform);
|
|
23420
23892
|
let projectRoot;
|
|
23421
|
-
const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(
|
|
23422
|
-
let cursor =
|
|
23893
|
+
const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path18.resolve(options.boundary), platform);
|
|
23894
|
+
let cursor = path18.resolve(options.cwd);
|
|
23423
23895
|
for (let depth = 0; depth < 256; depth++) {
|
|
23424
23896
|
const homeAnchorInvisible = normalizeForCompare(cursor, platform) === homeKey;
|
|
23425
|
-
if (!homeAnchorInvisible && await fs3.directoryExists(
|
|
23897
|
+
if (!homeAnchorInvisible && await fs3.directoryExists(path18.join(cursor, TANSR_DIR_NAME))) {
|
|
23426
23898
|
projectRoot = cursor;
|
|
23427
23899
|
break;
|
|
23428
23900
|
}
|
|
23429
23901
|
if (boundary !== void 0 && normalizeForCompare(cursor, platform) === boundary) break;
|
|
23430
|
-
const parent =
|
|
23902
|
+
const parent = path18.dirname(cursor);
|
|
23431
23903
|
if (parent === cursor) break;
|
|
23432
23904
|
cursor = parent;
|
|
23433
23905
|
}
|
|
@@ -23436,8 +23908,8 @@ async function discoverLayerFiles(options) {
|
|
|
23436
23908
|
managedCandidates,
|
|
23437
23909
|
...projectRoot !== void 0 ? {
|
|
23438
23910
|
projectRoot,
|
|
23439
|
-
projectSharedFile:
|
|
23440
|
-
projectLocalFile:
|
|
23911
|
+
projectSharedFile: path18.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
|
|
23912
|
+
projectLocalFile: path18.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
|
|
23441
23913
|
} : {},
|
|
23442
23914
|
userFile
|
|
23443
23915
|
};
|
|
@@ -23994,8 +24466,8 @@ function issueExpected(issue) {
|
|
|
23994
24466
|
return issue.code;
|
|
23995
24467
|
}
|
|
23996
24468
|
}
|
|
23997
|
-
function attributeLayer(layersDescending,
|
|
23998
|
-
return layersDescending.find((entry) => hasPath(entry.raw,
|
|
24469
|
+
function attributeLayer(layersDescending, path23) {
|
|
24470
|
+
return layersDescending.find((entry) => hasPath(entry.raw, path23));
|
|
23999
24471
|
}
|
|
24000
24472
|
function scanUnknownKeys(raw, parsed, pathSoFar, layersDescending, addDiagnostic) {
|
|
24001
24473
|
if (!isPlainObject2(raw) || !isPlainObject2(parsed)) return;
|
|
@@ -24168,7 +24640,7 @@ async function loadConfig(options = {}) {
|
|
|
24168
24640
|
const env = options.env ?? process.env;
|
|
24169
24641
|
const discovered = await discoverLayerFiles({
|
|
24170
24642
|
cwd: options.cwd ?? process.cwd(),
|
|
24171
|
-
homedir: options.homedir ??
|
|
24643
|
+
homedir: options.homedir ?? os3.homedir(),
|
|
24172
24644
|
env,
|
|
24173
24645
|
fs: fs3,
|
|
24174
24646
|
platform: options.platform ?? process.platform,
|
|
@@ -24248,9 +24720,9 @@ var OPEN_FENCE_RE = /^---[ \t]*\r?\n/;
|
|
|
24248
24720
|
var CLOSE_FENCE_RE = /^---[ \t]*$/;
|
|
24249
24721
|
function splitFrontmatterBlock(rawText) {
|
|
24250
24722
|
const text = stripBom2(rawText);
|
|
24251
|
-
const
|
|
24252
|
-
if (
|
|
24253
|
-
const rest = text.slice(
|
|
24723
|
+
const open4 = OPEN_FENCE_RE.exec(text);
|
|
24724
|
+
if (open4 === null) return null;
|
|
24725
|
+
const rest = text.slice(open4[0].length);
|
|
24254
24726
|
const lines = rest.split("\n");
|
|
24255
24727
|
for (let i = 0; i < lines.length; i++) {
|
|
24256
24728
|
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
@@ -25089,8 +25561,8 @@ var HookEngine = class {
|
|
|
25089
25561
|
|
|
25090
25562
|
// ../kernel/src/skills/discover.ts
|
|
25091
25563
|
import { promises as fsPromises2 } from "node:fs";
|
|
25092
|
-
import
|
|
25093
|
-
import
|
|
25564
|
+
import os4 from "node:os";
|
|
25565
|
+
import path19 from "node:path";
|
|
25094
25566
|
|
|
25095
25567
|
// ../kernel/src/skills/types.ts
|
|
25096
25568
|
var SKILLS_DIR_NAME = "skills";
|
|
@@ -25310,8 +25782,8 @@ async function scanFileSource(source, skillsDir, fs3, diagnostics) {
|
|
|
25310
25782
|
const seen = /* @__PURE__ */ new Set();
|
|
25311
25783
|
for (const dirName of [...subdirs].sort()) {
|
|
25312
25784
|
if (dirName.startsWith(".") || dirName.startsWith("_")) continue;
|
|
25313
|
-
const dir =
|
|
25314
|
-
const file =
|
|
25785
|
+
const dir = path19.join(skillsDir, dirName);
|
|
25786
|
+
const file = path19.join(dir, SKILL_FILE_NAME);
|
|
25315
25787
|
if (!SKILL_NAME_RE.test(dirName)) {
|
|
25316
25788
|
diagnostics.push({ severity: "warning", code: "name_invalid", source, skill: dirName, file });
|
|
25317
25789
|
continue;
|
|
@@ -25358,9 +25830,9 @@ async function discoverSkills(options = {}) {
|
|
|
25358
25830
|
const fs3 = options.fs ?? defaultSkillsFileSystem();
|
|
25359
25831
|
const env = options.env ?? process.env;
|
|
25360
25832
|
const platform = options.platform ?? process.platform;
|
|
25361
|
-
const homedir = options.homedir ??
|
|
25833
|
+
const homedir = options.homedir ?? os4.homedir();
|
|
25362
25834
|
const cwd = options.cwd ?? process.cwd();
|
|
25363
|
-
const explicitRoot = options.projectRoot === void 0 ? void 0 :
|
|
25835
|
+
const explicitRoot = options.projectRoot === void 0 ? void 0 : path19.resolve(options.projectRoot);
|
|
25364
25836
|
const discovered = await discoverLayerFiles({
|
|
25365
25837
|
cwd: explicitRoot ?? cwd,
|
|
25366
25838
|
homedir,
|
|
@@ -25371,13 +25843,13 @@ async function discoverSkills(options = {}) {
|
|
|
25371
25843
|
...explicitRoot !== void 0 ? { boundary: explicitRoot } : options.boundary !== void 0 ? { boundary: options.boundary } : {}
|
|
25372
25844
|
});
|
|
25373
25845
|
const builtinEntries = collectBuiltin(options.builtinSkills ?? [], diagnostics);
|
|
25374
|
-
const userSkillsDir =
|
|
25846
|
+
const userSkillsDir = path19.join(path19.dirname(discovered.userFile), SKILLS_DIR_NAME);
|
|
25375
25847
|
const userOutcome = await scanFileSource("user", userSkillsDir, fs3, diagnostics);
|
|
25376
25848
|
const projectRoot = explicitRoot ?? discovered.projectRoot;
|
|
25377
25849
|
let projectSkillsDir;
|
|
25378
25850
|
let projectOutcome = { status: "missing" };
|
|
25379
25851
|
if (projectRoot !== void 0) {
|
|
25380
|
-
projectSkillsDir =
|
|
25852
|
+
projectSkillsDir = path19.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
|
|
25381
25853
|
projectOutcome = await scanFileSource("project", projectSkillsDir, fs3, diagnostics);
|
|
25382
25854
|
}
|
|
25383
25855
|
const userEntries = userOutcome.status === "scanned" ? userOutcome.entries : [];
|
|
@@ -25451,7 +25923,7 @@ async function discoverSkills(options = {}) {
|
|
|
25451
25923
|
}
|
|
25452
25924
|
|
|
25453
25925
|
// ../kernel/src/skills/registry.ts
|
|
25454
|
-
import
|
|
25926
|
+
import path20 from "node:path";
|
|
25455
25927
|
function errCode2(err) {
|
|
25456
25928
|
if (err !== null && typeof err === "object" && "code" in err) {
|
|
25457
25929
|
const code = err.code;
|
|
@@ -25514,7 +25986,7 @@ var SkillRegistry = class {
|
|
|
25514
25986
|
/** 技能目录绝对路径(正文相对引用锚点;builtin 无) */
|
|
25515
25987
|
static baseDirOf(entry) {
|
|
25516
25988
|
if (entry.dir !== void 0) return entry.dir;
|
|
25517
|
-
if (entry.file !== void 0) return
|
|
25989
|
+
if (entry.file !== void 0) return path20.dirname(entry.file);
|
|
25518
25990
|
return void 0;
|
|
25519
25991
|
}
|
|
25520
25992
|
};
|
|
@@ -25718,9 +26190,9 @@ function defineSkill(options) {
|
|
|
25718
26190
|
];
|
|
25719
26191
|
return { name: options.name, content: lines.join("\n") };
|
|
25720
26192
|
}
|
|
25721
|
-
var NO_DISCOVERY_ROOT =
|
|
26193
|
+
var NO_DISCOVERY_ROOT = path21.resolve(path21.sep, ".tansr-sdk-no-discovery");
|
|
25722
26194
|
function noDiscoveryFs(inner) {
|
|
25723
|
-
const inVoid = (target) =>
|
|
26195
|
+
const inVoid = (target) => path21.resolve(target).startsWith(NO_DISCOVERY_ROOT);
|
|
25724
26196
|
const enoent = (target) => {
|
|
25725
26197
|
const err = new Error(`ENOENT: no such file or directory ${target}`);
|
|
25726
26198
|
err.code = "ENOENT";
|
|
@@ -25734,7 +26206,7 @@ function noDiscoveryFs(inner) {
|
|
|
25734
26206
|
}
|
|
25735
26207
|
async function assembleSdkSkills(options) {
|
|
25736
26208
|
const fs3 = noDiscoveryFs(options.fs ?? defaultSkillsFileSystem());
|
|
25737
|
-
const dirs = (options.dirs ?? []).map((dir) =>
|
|
26209
|
+
const dirs = (options.dirs ?? []).map((dir) => path21.resolve(dir));
|
|
25738
26210
|
for (const dir of dirs) {
|
|
25739
26211
|
if (!await fs3.directoryExists(dir)) {
|
|
25740
26212
|
throw new TansrSdkError(
|
|
@@ -27870,7 +28342,7 @@ var OpenAIResponsesAdapter = class {
|
|
|
27870
28342
|
};
|
|
27871
28343
|
|
|
27872
28344
|
// ../providers/src/retry/attempt-observer.ts
|
|
27873
|
-
import { createHash as
|
|
28345
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
27874
28346
|
var PROVIDER_CALL_META_FIELD = "callMeta";
|
|
27875
28347
|
function providerCallMetaOf(options) {
|
|
27876
28348
|
if (options === void 0) return void 0;
|
|
@@ -27899,12 +28371,12 @@ function endpointKeyOf(baseUrl) {
|
|
|
27899
28371
|
let normalized;
|
|
27900
28372
|
try {
|
|
27901
28373
|
const url = new URL(baseUrl);
|
|
27902
|
-
const
|
|
27903
|
-
normalized = `${url.protocol}//${url.host}${
|
|
28374
|
+
const path23 = url.pathname.replace(/\/+$/, "");
|
|
28375
|
+
normalized = `${url.protocol}//${url.host}${path23}`.toLowerCase();
|
|
27904
28376
|
} catch {
|
|
27905
28377
|
normalized = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
|
|
27906
28378
|
}
|
|
27907
|
-
return
|
|
28379
|
+
return createHash10("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
|
|
27908
28380
|
}
|
|
27909
28381
|
function safeAttemptCall(fn) {
|
|
27910
28382
|
if (fn === void 0) return;
|
|
@@ -28041,14 +28513,14 @@ function sendableTwpReasoningOff(features) {
|
|
|
28041
28513
|
}
|
|
28042
28514
|
|
|
28043
28515
|
// ../providers/src/twp/signing.ts
|
|
28044
|
-
import { createHash as
|
|
28516
|
+
import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
|
|
28045
28517
|
var TWP_SIGNING_ALGORITHM = "TWP1-HMAC-SHA256";
|
|
28046
28518
|
var TWP_NONCE_LENGTH = 24;
|
|
28047
28519
|
function deriveTwpSigningKey(secretKey) {
|
|
28048
|
-
return
|
|
28520
|
+
return createHash11("sha256").update(secretKey, "utf8").digest();
|
|
28049
28521
|
}
|
|
28050
28522
|
function sha256Hex(data) {
|
|
28051
|
-
return
|
|
28523
|
+
return createHash11("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
|
|
28052
28524
|
}
|
|
28053
28525
|
function generateTwpNonce(random = randomBytes) {
|
|
28054
28526
|
return random(TWP_NONCE_LENGTH / 2).toString("hex");
|
|
@@ -28290,11 +28762,11 @@ function twpHttpStatusToIR(status, bodyText) {
|
|
|
28290
28762
|
if (status >= 500) return { kind: "server", message, recoverable: true };
|
|
28291
28763
|
return { kind: "invalid_request", message, recoverable: false };
|
|
28292
28764
|
}
|
|
28293
|
-
function
|
|
28765
|
+
function isRecord7(value) {
|
|
28294
28766
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28295
28767
|
}
|
|
28296
28768
|
function twpNonStreamToEvents(raw, fallbackModel) {
|
|
28297
|
-
if (!
|
|
28769
|
+
if (!isRecord7(raw)) {
|
|
28298
28770
|
return [
|
|
28299
28771
|
{
|
|
28300
28772
|
t: "error",
|
|
@@ -28312,7 +28784,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28312
28784
|
const blocks = Array.isArray(raw["blocks"]) ? raw["blocks"] : [];
|
|
28313
28785
|
let index = 0;
|
|
28314
28786
|
for (const rawBlock of blocks) {
|
|
28315
|
-
if (!
|
|
28787
|
+
if (!isRecord7(rawBlock)) continue;
|
|
28316
28788
|
if (rawBlock["t"] === "text" && typeof rawBlock["v"] === "string") {
|
|
28317
28789
|
out.push({ t: "block_start", index, block: { t: "text", text: rawBlock["v"] } });
|
|
28318
28790
|
out.push({ t: "block_stop", index });
|
|
@@ -28332,7 +28804,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28332
28804
|
index += 1;
|
|
28333
28805
|
}
|
|
28334
28806
|
}
|
|
28335
|
-
const usage =
|
|
28807
|
+
const usage = isRecord7(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
|
|
28336
28808
|
out.push({
|
|
28337
28809
|
t: "message_stop",
|
|
28338
28810
|
stopReason: mapTwpStop(typeof raw["stop"] === "string" ? raw["stop"] : void 0),
|
|
@@ -28341,7 +28813,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
|
|
|
28341
28813
|
return out;
|
|
28342
28814
|
}
|
|
28343
28815
|
function twpNonStreamUsageFrame(raw) {
|
|
28344
|
-
if (!
|
|
28816
|
+
if (!isRecord7(raw) || !isRecord7(raw["usage"])) return null;
|
|
28345
28817
|
return parseTwpUsage(raw["usage"]);
|
|
28346
28818
|
}
|
|
28347
28819
|
|
|
@@ -29023,9 +29495,9 @@ var MissingApiKeyError = class extends ProviderRegistryError {
|
|
|
29023
29495
|
var CyclicFallbackError = class extends ProviderRegistryError {
|
|
29024
29496
|
/** 成环路径,末项为再次出现的别名,如 ['main','fast','main'] */
|
|
29025
29497
|
path;
|
|
29026
|
-
constructor(
|
|
29027
|
-
super("cyclic_fallback", `Cyclic fallback chain: ${
|
|
29028
|
-
this.path =
|
|
29498
|
+
constructor(path23) {
|
|
29499
|
+
super("cyclic_fallback", `Cyclic fallback chain: ${path23.join(" -> ")}`);
|
|
29500
|
+
this.path = path23;
|
|
29029
29501
|
}
|
|
29030
29502
|
};
|
|
29031
29503
|
|
|
@@ -29508,9 +29980,9 @@ function pushUnique(out, seen, resolved) {
|
|
|
29508
29980
|
seen.add(dedupeKey);
|
|
29509
29981
|
out.push(resolved);
|
|
29510
29982
|
}
|
|
29511
|
-
function expandInto(config, alias,
|
|
29512
|
-
if (
|
|
29513
|
-
const nextPath = [...
|
|
29983
|
+
function expandInto(config, alias, path23, out, seen) {
|
|
29984
|
+
if (path23.includes(alias)) throw new CyclicFallbackError([...path23, alias]);
|
|
29985
|
+
const nextPath = [...path23, alias];
|
|
29514
29986
|
pushUnique(out, seen, resolveAliasOrRef(config, alias));
|
|
29515
29987
|
for (const entry of config.fallbacks[alias] ?? []) {
|
|
29516
29988
|
if (entry.includes("/")) {
|
|
@@ -30337,8 +30809,10 @@ var AppQuotaSchema = z41.object({
|
|
|
30337
30809
|
import { z as z42 } from "zod";
|
|
30338
30810
|
var IMAGEGEN_TOOL_MAX_N = 4;
|
|
30339
30811
|
var ImageGenArgsSchema = z42.object({
|
|
30340
|
-
|
|
30341
|
-
|
|
30812
|
+
// S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
|
|
30813
|
+
// 第一生效(与 bundle platformModels.imageGen 首行恒同);点名限集内(工具描述自列)。
|
|
30814
|
+
model: z42.string().min(1).optional().describe(
|
|
30815
|
+
"Image model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model)."
|
|
30342
30816
|
),
|
|
30343
30817
|
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
30818
|
negativePrompt: z42.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the image."),
|
|
@@ -30368,23 +30842,34 @@ function hintOf(code) {
|
|
|
30368
30842
|
}
|
|
30369
30843
|
return "";
|
|
30370
30844
|
}
|
|
30845
|
+
function authorizedModelsNote(models) {
|
|
30846
|
+
if (models === void 0) {
|
|
30847
|
+
return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
|
|
30848
|
+
}
|
|
30849
|
+
if (models.length === 0) {
|
|
30850
|
+
return " The platform has no image model configured for this app yet — tell the user instead of retrying or guessing model names.";
|
|
30851
|
+
}
|
|
30852
|
+
const listed = models.map((m) => m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model).join(", ");
|
|
30853
|
+
return ` Authorized image models (omit "model" to use the first as default): ${listed}.`;
|
|
30854
|
+
}
|
|
30371
30855
|
function createImageGenTool(options) {
|
|
30372
30856
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30373
30857
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30374
30858
|
const token = options.token;
|
|
30375
30859
|
return {
|
|
30376
30860
|
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
|
|
30861
|
+
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),
|
|
30862
|
+
shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model?, prompt, negativePrompt?, size?, n?, seed?.",
|
|
30379
30863
|
inputSchema: ImageGenArgsSchema,
|
|
30380
30864
|
isReadOnly: false,
|
|
30381
30865
|
isConcurrencySafe: false,
|
|
30382
30866
|
async execute(args, ctx) {
|
|
30867
|
+
const modelRef = args.model ?? "(default)";
|
|
30383
30868
|
if (ctx.signal.aborted) {
|
|
30384
|
-
return errorResult13("Tool execution was aborted.",
|
|
30869
|
+
return errorResult13("Tool execution was aborted.", modelRef);
|
|
30385
30870
|
}
|
|
30386
30871
|
const payload = {
|
|
30387
|
-
model: args.model,
|
|
30872
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
30388
30873
|
prompt: args.prompt,
|
|
30389
30874
|
...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
|
|
30390
30875
|
...args.size !== void 0 ? { size: args.size } : {},
|
|
@@ -30404,9 +30889,9 @@ function createImageGenTool(options) {
|
|
|
30404
30889
|
signal: ctx.signal
|
|
30405
30890
|
});
|
|
30406
30891
|
} catch (err) {
|
|
30407
|
-
if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.",
|
|
30892
|
+
if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", modelRef);
|
|
30408
30893
|
const message = err instanceof Error ? err.message : String(err);
|
|
30409
|
-
return errorResult13(`Image generation request failed to reach the platform: ${message}`,
|
|
30894
|
+
return errorResult13(`Image generation request failed to reach the platform: ${message}`, modelRef);
|
|
30410
30895
|
}
|
|
30411
30896
|
let raw = null;
|
|
30412
30897
|
try {
|
|
@@ -30418,17 +30903,18 @@ function createImageGenTool(options) {
|
|
|
30418
30903
|
const envelope = raw ?? {};
|
|
30419
30904
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30420
30905
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30421
|
-
return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`,
|
|
30906
|
+
return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`, modelRef, code);
|
|
30422
30907
|
}
|
|
30423
30908
|
const body = raw ?? {};
|
|
30424
30909
|
const images = Array.isArray(body.images) ? body.images.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
|
|
30425
30910
|
const imageCount = typeof body.imageCount === "number" ? body.imageCount : images.length;
|
|
30426
30911
|
if (images.length === 0) {
|
|
30427
|
-
return errorResult13("Image generation returned no image URL (unexpected platform response).",
|
|
30912
|
+
return errorResult13("Image generation returned no image URL (unexpected platform response).", modelRef);
|
|
30428
30913
|
}
|
|
30429
|
-
const
|
|
30914
|
+
const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
|
|
30915
|
+
const data = { model: resolvedModel, images, imageCount };
|
|
30430
30916
|
const lines = [
|
|
30431
|
-
`Generated ${imageCount} image(s) with model ${
|
|
30917
|
+
`Generated ${imageCount} image(s) with model ${resolvedModel} (billed per image).`,
|
|
30432
30918
|
"Image URLs (valid ~24h; surface or persist promptly):",
|
|
30433
30919
|
...images.map((img, i) => `${i + 1}. ${img.url}`)
|
|
30434
30920
|
];
|
|
@@ -30441,8 +30927,10 @@ function createImageGenTool(options) {
|
|
|
30441
30927
|
import { z as z43 } from "zod";
|
|
30442
30928
|
var VIDEOGEN_TOOL_MAX_DURATION = 30;
|
|
30443
30929
|
var VideoGenArgsSchema = z43.object({
|
|
30444
|
-
|
|
30445
|
-
|
|
30930
|
+
// S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
|
|
30931
|
+
// 第一生效(与 bundle platformModels.videoGen 首行恒同);点名限集内(工具描述自列)。
|
|
30932
|
+
model: z43.string().min(1).optional().describe(
|
|
30933
|
+
"Video model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model)."
|
|
30446
30934
|
),
|
|
30447
30935
|
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
30936
|
negativePrompt: z43.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the video."),
|
|
@@ -30480,23 +30968,34 @@ function hintOf2(code) {
|
|
|
30480
30968
|
}
|
|
30481
30969
|
return "";
|
|
30482
30970
|
}
|
|
30971
|
+
function authorizedModelsNote2(models) {
|
|
30972
|
+
if (models === void 0) {
|
|
30973
|
+
return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
|
|
30974
|
+
}
|
|
30975
|
+
if (models.length === 0) {
|
|
30976
|
+
return " The platform has no video model configured for this app yet — tell the user instead of retrying or guessing model names.";
|
|
30977
|
+
}
|
|
30978
|
+
const listed = models.map((m) => m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model).join(", ");
|
|
30979
|
+
return ` Authorized video models (omit "model" to use the first as default): ${listed}.`;
|
|
30980
|
+
}
|
|
30483
30981
|
function createVideoGenTool(options) {
|
|
30484
30982
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30485
30983
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30486
30984
|
const token = options.token;
|
|
30487
30985
|
return {
|
|
30488
30986
|
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
|
|
30987
|
+
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),
|
|
30988
|
+
shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model?, prompt, negativePrompt?, duration?, ratio?, seed?.",
|
|
30491
30989
|
inputSchema: VideoGenArgsSchema,
|
|
30492
30990
|
isReadOnly: false,
|
|
30493
30991
|
isConcurrencySafe: false,
|
|
30494
30992
|
async execute(args, ctx) {
|
|
30993
|
+
const modelRef = args.model ?? "(default)";
|
|
30495
30994
|
if (ctx.signal.aborted) {
|
|
30496
|
-
return errorResult14("Tool execution was aborted.",
|
|
30995
|
+
return errorResult14("Tool execution was aborted.", modelRef);
|
|
30497
30996
|
}
|
|
30498
30997
|
const payload = {
|
|
30499
|
-
model: args.model,
|
|
30998
|
+
...args.model !== void 0 ? { model: args.model } : {},
|
|
30500
30999
|
prompt: args.prompt,
|
|
30501
31000
|
...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
|
|
30502
31001
|
...args.duration !== void 0 ? { duration: args.duration } : {},
|
|
@@ -30516,9 +31015,9 @@ function createVideoGenTool(options) {
|
|
|
30516
31015
|
signal: ctx.signal
|
|
30517
31016
|
});
|
|
30518
31017
|
} catch (err) {
|
|
30519
|
-
if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.",
|
|
31018
|
+
if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", modelRef);
|
|
30520
31019
|
const message = err instanceof Error ? err.message : String(err);
|
|
30521
|
-
return errorResult14(`Video generation request failed to reach the platform: ${message}`,
|
|
31020
|
+
return errorResult14(`Video generation request failed to reach the platform: ${message}`, modelRef);
|
|
30522
31021
|
}
|
|
30523
31022
|
let raw = null;
|
|
30524
31023
|
try {
|
|
@@ -30530,18 +31029,19 @@ function createVideoGenTool(options) {
|
|
|
30530
31029
|
const envelope = raw ?? {};
|
|
30531
31030
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30532
31031
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30533
|
-
return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`,
|
|
31032
|
+
return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`, modelRef, code);
|
|
30534
31033
|
}
|
|
30535
31034
|
const body = raw ?? {};
|
|
30536
31035
|
const videos = Array.isArray(body.videos) ? body.videos.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
|
|
30537
31036
|
if (videos.length === 0) {
|
|
30538
|
-
return errorResult14("Video generation returned no video URL (unexpected platform response).",
|
|
31037
|
+
return errorResult14("Video generation returned no video URL (unexpected platform response).", modelRef);
|
|
30539
31038
|
}
|
|
30540
31039
|
const videoCount = typeof body.videoCount === "number" ? body.videoCount : videos.length;
|
|
30541
31040
|
const billedSeconds = typeof body.billedSeconds === "number" ? body.billedSeconds : 0;
|
|
30542
|
-
const
|
|
31041
|
+
const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
|
|
31042
|
+
const data = { model: resolvedModel, videos, videoCount, billedSeconds };
|
|
30543
31043
|
const lines = [
|
|
30544
|
-
`Generated ${videoCount} video(s) with model ${
|
|
31044
|
+
`Generated ${videoCount} video(s) with model ${resolvedModel} (${billedSeconds}s billed, per-second pricing).`,
|
|
30545
31045
|
"Video URLs (valid ~24h; surface or persist promptly):",
|
|
30546
31046
|
...videos.map((v, i) => `${i + 1}. ${v.url}`)
|
|
30547
31047
|
];
|
|
@@ -30550,24 +31050,8 @@ function createVideoGenTool(options) {
|
|
|
30550
31050
|
};
|
|
30551
31051
|
}
|
|
30552
31052
|
|
|
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
|
-
}
|
|
31053
|
+
// src/platform/websearch-provider.ts
|
|
31054
|
+
var PLATFORM_SEARCH_PROVIDER_NAME = "tansr-platform";
|
|
30571
31055
|
function hintOf3(code) {
|
|
30572
31056
|
if (code === "forbidden") {
|
|
30573
31057
|
return " The app platform capability webSearch is disabled; the app developer can enable it in console → app → capabilities.";
|
|
@@ -30589,25 +31073,13 @@ function hintOf3(code) {
|
|
|
30589
31073
|
}
|
|
30590
31074
|
return "";
|
|
30591
31075
|
}
|
|
30592
|
-
function
|
|
31076
|
+
function createPlatformSearchProvider(options) {
|
|
30593
31077
|
const base = options.baseUrl.replace(/\/+$/, "");
|
|
30594
31078
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
30595
31079
|
const token = options.token;
|
|
30596
31080
|
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
|
-
};
|
|
31081
|
+
name: PLATFORM_SEARCH_PROVIDER_NAME,
|
|
31082
|
+
async search(query2, context) {
|
|
30611
31083
|
let response;
|
|
30612
31084
|
try {
|
|
30613
31085
|
response = await fetchImpl(`${base}/t1/websearch`, {
|
|
@@ -30617,13 +31089,15 @@ function createWebSearchTool2(options) {
|
|
|
30617
31089
|
accept: "application/json",
|
|
30618
31090
|
[HEADER_APP_TOKEN]: token
|
|
30619
31091
|
},
|
|
30620
|
-
|
|
30621
|
-
|
|
31092
|
+
// wire 体 snake_case(CLI 内核 HTTP 后端字节同构的既有特例);
|
|
31093
|
+
// maxResults 已被 kernel 工具层夹紧(缺省 8 / 上限 20,与网关帽同值)。
|
|
31094
|
+
body: JSON.stringify({ query: query2.query, max_results: query2.maxResults }),
|
|
31095
|
+
signal: context.signal
|
|
30622
31096
|
});
|
|
30623
31097
|
} catch (err) {
|
|
30624
|
-
if (
|
|
31098
|
+
if (context.signal.aborted) throw err;
|
|
30625
31099
|
const message = err instanceof Error ? err.message : String(err);
|
|
30626
|
-
|
|
31100
|
+
throw new Error(`web search request failed to reach the tansr platform: ${message}`);
|
|
30627
31101
|
}
|
|
30628
31102
|
let raw = null;
|
|
30629
31103
|
try {
|
|
@@ -30635,13 +31109,13 @@ function createWebSearchTool2(options) {
|
|
|
30635
31109
|
const envelope = raw ?? {};
|
|
30636
31110
|
const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
|
|
30637
31111
|
const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
|
|
30638
|
-
|
|
31112
|
+
throw new Error(`platform web search failed (${code}): ${message}.${hintOf3(code)}`);
|
|
30639
31113
|
}
|
|
30640
31114
|
const body = raw ?? {};
|
|
30641
31115
|
if (!Array.isArray(body.results)) {
|
|
30642
|
-
|
|
31116
|
+
throw new Error("platform web search returned an unexpected response (no results array).");
|
|
30643
31117
|
}
|
|
30644
|
-
|
|
31118
|
+
return body.results.map((item) => {
|
|
30645
31119
|
const row = item ?? {};
|
|
30646
31120
|
if (typeof row.title !== "string" || typeof row.url !== "string") return null;
|
|
30647
31121
|
return {
|
|
@@ -30650,19 +31124,6 @@ function createWebSearchTool2(options) {
|
|
|
30650
31124
|
snippet: typeof row.snippet === "string" ? row.snippet : ""
|
|
30651
31125
|
};
|
|
30652
31126
|
}).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
31127
|
}
|
|
30667
31128
|
};
|
|
30668
31129
|
}
|
|
@@ -30682,7 +31143,7 @@ var BUILTIN_ORDER = [
|
|
|
30682
31143
|
"webSearch",
|
|
30683
31144
|
"http"
|
|
30684
31145
|
];
|
|
30685
|
-
var PLATFORM_NAMES = ["imageGen", "videoGen"
|
|
31146
|
+
var PLATFORM_NAMES = ["imageGen", "videoGen"];
|
|
30686
31147
|
function toToolDef(tool) {
|
|
30687
31148
|
const converted = zodToJsonSchema(tool.inputSchema);
|
|
30688
31149
|
const { $schema: _dropped, ...inputSchema } = converted;
|
|
@@ -30718,6 +31179,12 @@ function resolvePlatformSelection(selection, capabilities) {
|
|
|
30718
31179
|
if (selection === void 0) return [];
|
|
30719
31180
|
const requested = /* @__PURE__ */ new Set();
|
|
30720
31181
|
for (const name of selection) {
|
|
31182
|
+
if (name === "webSearch") {
|
|
31183
|
+
throw new TansrSdkError(
|
|
31184
|
+
"invalid_options",
|
|
31185
|
+
"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)."
|
|
31186
|
+
);
|
|
31187
|
+
}
|
|
30721
31188
|
if (!PLATFORM_NAMES.includes(name)) {
|
|
30722
31189
|
throw new TansrSdkError(
|
|
30723
31190
|
"invalid_options",
|
|
@@ -30785,7 +31252,7 @@ function buildBuiltinTool(name, materials) {
|
|
|
30785
31252
|
case "webFetch":
|
|
30786
31253
|
return createWebFetchTool();
|
|
30787
31254
|
case "webSearch":
|
|
30788
|
-
return createWebSearchTool({ provider:
|
|
31255
|
+
return createWebSearchTool({ provider: materials.searchProvider });
|
|
30789
31256
|
case "http":
|
|
30790
31257
|
return createHttpTool();
|
|
30791
31258
|
}
|
|
@@ -30793,9 +31260,37 @@ function buildBuiltinTool(name, materials) {
|
|
|
30793
31260
|
function buildSdkToolSet(options = {}) {
|
|
30794
31261
|
const capabilities = options.capabilities ?? DEFAULT_APP_CAPABILITIES;
|
|
30795
31262
|
const selection = options.tools ?? {};
|
|
30796
|
-
|
|
31263
|
+
let builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
|
|
30797
31264
|
const platformNames = resolvePlatformSelection(selection.platform, capabilities);
|
|
30798
31265
|
validateCustomSelection(selection.custom, capabilities);
|
|
31266
|
+
let webSearchProvider = null;
|
|
31267
|
+
if (builtinNames.includes("webSearch")) {
|
|
31268
|
+
const pc = options.platformContext;
|
|
31269
|
+
const explicit = selection.builtin !== void 0;
|
|
31270
|
+
if (pc === void 0) {
|
|
31271
|
+
if (explicit) {
|
|
31272
|
+
throw new TansrSdkError(
|
|
31273
|
+
"invalid_options",
|
|
31274
|
+
"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."
|
|
31275
|
+
);
|
|
31276
|
+
}
|
|
31277
|
+
builtinNames = builtinNames.filter((name) => name !== "webSearch");
|
|
31278
|
+
} else if (!capabilities.platform.webSearch) {
|
|
31279
|
+
if (explicit) {
|
|
31280
|
+
throw new TansrSdkError(
|
|
31281
|
+
"capability_disabled",
|
|
31282
|
+
`App platform capability 'webSearch' is disabled; ${CONSOLE_HINT}, or remove 'webSearch' from tools.builtin.`
|
|
31283
|
+
);
|
|
31284
|
+
}
|
|
31285
|
+
builtinNames = builtinNames.filter((name) => name !== "webSearch");
|
|
31286
|
+
} else {
|
|
31287
|
+
webSearchProvider = createPlatformSearchProvider({
|
|
31288
|
+
baseUrl: pc.baseUrl,
|
|
31289
|
+
token: pc.token,
|
|
31290
|
+
...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
|
|
31291
|
+
});
|
|
31292
|
+
}
|
|
31293
|
+
}
|
|
30799
31294
|
const platformTools = [];
|
|
30800
31295
|
for (const name of platformNames) {
|
|
30801
31296
|
const pc = options.platformContext;
|
|
@@ -30811,14 +31306,14 @@ function buildSdkToolSet(options = {}) {
|
|
|
30811
31306
|
...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
|
|
30812
31307
|
};
|
|
30813
31308
|
platformTools.push(
|
|
30814
|
-
name === "imageGen" ? createImageGenTool(materials
|
|
31309
|
+
name === "imageGen" ? createImageGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.imageGen } : {} }) : createVideoGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.videoGen } : {} })
|
|
30815
31310
|
);
|
|
30816
31311
|
}
|
|
30817
31312
|
const store = options.store ?? new TodoStore();
|
|
30818
31313
|
const channel = options.promptChannel ?? new UnavailableChannel();
|
|
30819
31314
|
const custom = selection.custom ?? [];
|
|
30820
31315
|
const tools = [
|
|
30821
|
-
...builtinNames.map((name) => buildBuiltinTool(name, { store, channel })),
|
|
31316
|
+
...builtinNames.map((name) => buildBuiltinTool(name, { store, channel, searchProvider: webSearchProvider })),
|
|
30822
31317
|
...platformTools,
|
|
30823
31318
|
...custom
|
|
30824
31319
|
];
|
|
@@ -31001,11 +31496,11 @@ async function assembleTooling(options) {
|
|
|
31001
31496
|
var TANSR_PROVIDER_ID = "tansr";
|
|
31002
31497
|
var APP_TOKEN_PLACEHOLDER_ENV = "TANSR_SDK_APP_TOKEN_TIER";
|
|
31003
31498
|
var APP_TOKEN_PLACEHOLDER_VALUE = "app-token-tier";
|
|
31004
|
-
function
|
|
31499
|
+
function isRecord8(value) {
|
|
31005
31500
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31006
31501
|
}
|
|
31007
31502
|
function parseModel(raw) {
|
|
31008
|
-
if (!
|
|
31503
|
+
if (!isRecord8(raw)) return null;
|
|
31009
31504
|
if (typeof raw.handle !== "string" || raw.handle === "" || typeof raw.modelId !== "string" || raw.modelId === "" || typeof raw.protocol !== "string") {
|
|
31010
31505
|
return null;
|
|
31011
31506
|
}
|
|
@@ -31013,25 +31508,45 @@ function parseModel(raw) {
|
|
|
31013
31508
|
handle: raw.handle,
|
|
31014
31509
|
modelId: raw.modelId,
|
|
31015
31510
|
displayName: typeof raw.displayName === "string" ? raw.displayName : raw.handle,
|
|
31511
|
+
// 模型呈现面二波(④)加法解析:缺键/坏形状 = null(旧 bundle 回落,恒不猜)
|
|
31512
|
+
manufacturer: typeof raw.manufacturer === "string" && raw.manufacturer !== "" ? raw.manufacturer : null,
|
|
31513
|
+
family: typeof raw.family === "string" && raw.family !== "" ? raw.family : null,
|
|
31016
31514
|
protocol: raw.protocol,
|
|
31017
|
-
capabilities:
|
|
31515
|
+
capabilities: isRecord8(raw.capabilities) ? raw.capabilities : {},
|
|
31018
31516
|
contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null
|
|
31019
31517
|
};
|
|
31020
31518
|
}
|
|
31519
|
+
function parseMediaModels(raw) {
|
|
31520
|
+
if (!Array.isArray(raw)) return [];
|
|
31521
|
+
const out = [];
|
|
31522
|
+
for (const item of raw) {
|
|
31523
|
+
if (!isRecord8(item) || typeof item.model !== "string" || item.model === "") continue;
|
|
31524
|
+
out.push({
|
|
31525
|
+
model: item.model,
|
|
31526
|
+
displayName: typeof item.displayName === "string" && item.displayName !== "" ? item.displayName : item.model
|
|
31527
|
+
});
|
|
31528
|
+
}
|
|
31529
|
+
return out;
|
|
31530
|
+
}
|
|
31021
31531
|
function parseAppBundle(raw) {
|
|
31022
|
-
if (!
|
|
31532
|
+
if (!isRecord8(raw)) return null;
|
|
31023
31533
|
const models = Array.isArray(raw.models) ? raw.models.map(parseModel).filter((m) => m !== null) : [];
|
|
31024
31534
|
const aliases = {};
|
|
31025
|
-
if (
|
|
31535
|
+
if (isRecord8(raw.aliases)) {
|
|
31026
31536
|
for (const [key2, value] of Object.entries(raw.aliases)) {
|
|
31027
31537
|
if (typeof value === "string" && value !== "") aliases[key2] = value;
|
|
31028
31538
|
}
|
|
31029
31539
|
}
|
|
31030
31540
|
const parsedCaps = AppCapabilitiesSchema.safeParse(raw.capabilities);
|
|
31541
|
+
const mediaRaw = isRecord8(raw.platformModels) ? raw.platformModels : {};
|
|
31031
31542
|
return {
|
|
31032
31543
|
models,
|
|
31033
31544
|
aliases,
|
|
31034
|
-
capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES
|
|
31545
|
+
capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES,
|
|
31546
|
+
platformModels: {
|
|
31547
|
+
imageGen: parseMediaModels(mediaRaw.imageGen),
|
|
31548
|
+
videoGen: parseMediaModels(mediaRaw.videoGen)
|
|
31549
|
+
}
|
|
31035
31550
|
};
|
|
31036
31551
|
}
|
|
31037
31552
|
function sanitizeModelCapabilities(capabilities, contextWindow) {
|
|
@@ -31172,10 +31687,13 @@ async function assemblePlatformModel(options) {
|
|
|
31172
31687
|
const registry = new ProviderRegistry(appBundleRegistryConfig(bundle, base), {
|
|
31173
31688
|
// 占位哑值:满足装配期 readApiKey 非空校验;真实鉴权恒由 fetch 包装注入
|
|
31174
31689
|
env: { [APP_TOKEN_PLACEHOLDER_ENV]: APP_TOKEN_PLACEHOLDER_VALUE },
|
|
31175
|
-
fetchImpl: createAppTokenFetch(options.token, options.fetchImpl)
|
|
31690
|
+
fetchImpl: createAppTokenFetch(options.token, options.fetchImpl),
|
|
31691
|
+
// 拍板⑤:平台会话 id 供给经既有 TwpClientOptions.sessionId 缝出线——
|
|
31692
|
+
// registry 为会话私有(每次装配新建),setModel/Task 子代理同缝同值
|
|
31693
|
+
...options.sessionId !== void 0 ? { twp: { sessionId: options.sessionId } } : {}
|
|
31176
31694
|
});
|
|
31177
31695
|
const built = buildClientFromRegistry(registry, options.model ?? "main", options.onProviderSelected);
|
|
31178
|
-
return { ...built, registry, capabilities: bundle.capabilities };
|
|
31696
|
+
return { ...built, registry, capabilities: bundle.capabilities, platformModels: bundle.platformModels };
|
|
31179
31697
|
}
|
|
31180
31698
|
|
|
31181
31699
|
// src/task.ts
|
|
@@ -31264,7 +31782,9 @@ async function* consumeManaged(options) {
|
|
|
31264
31782
|
platformContext = {
|
|
31265
31783
|
baseUrl: options.baseUrl,
|
|
31266
31784
|
token: options.token,
|
|
31267
|
-
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
31785
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
31786
|
+
// S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
|
|
31787
|
+
platformModels: platform.platformModels
|
|
31268
31788
|
};
|
|
31269
31789
|
} else {
|
|
31270
31790
|
const managed = await assembleManagedModel({
|
|
@@ -31362,6 +31882,60 @@ async function* consumeHandle(handle) {
|
|
|
31362
31882
|
|
|
31363
31883
|
// src/session.ts
|
|
31364
31884
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
31885
|
+
|
|
31886
|
+
// src/sessions/pairing.ts
|
|
31887
|
+
var SYNTHETIC_TOOL_RESULT_TEXT = "Tool execution was interrupted before its result was persisted; this placeholder was synthesized while resuming the session.";
|
|
31888
|
+
function toolCallIds(message) {
|
|
31889
|
+
return message.blocks.flatMap((b) => b.t === "tool_call" ? [b.id] : []);
|
|
31890
|
+
}
|
|
31891
|
+
function toolResultIds(message) {
|
|
31892
|
+
return message.blocks.flatMap((b) => b.t === "tool_result" ? [b.callId] : []);
|
|
31893
|
+
}
|
|
31894
|
+
function repairHistoryPairing(messages) {
|
|
31895
|
+
const open4 = /* @__PURE__ */ new Set();
|
|
31896
|
+
let cleanEnd = 0;
|
|
31897
|
+
for (let i = 0; i < messages.length; i++) {
|
|
31898
|
+
const message = messages[i];
|
|
31899
|
+
if (message.role === "assistant") {
|
|
31900
|
+
for (const id of toolCallIds(message)) open4.add(id);
|
|
31901
|
+
} else {
|
|
31902
|
+
for (const callId of toolResultIds(message)) {
|
|
31903
|
+
if (!open4.has(callId)) {
|
|
31904
|
+
return {
|
|
31905
|
+
messages: messages.slice(0, cleanEnd).map((m) => structuredClone(m)),
|
|
31906
|
+
synthesizedResults: 0,
|
|
31907
|
+
droppedMessages: messages.length - cleanEnd
|
|
31908
|
+
};
|
|
31909
|
+
}
|
|
31910
|
+
open4.delete(callId);
|
|
31911
|
+
}
|
|
31912
|
+
}
|
|
31913
|
+
if (open4.size === 0) cleanEnd = i + 1;
|
|
31914
|
+
}
|
|
31915
|
+
if (open4.size === 0) {
|
|
31916
|
+
return {
|
|
31917
|
+
messages: messages.map((m) => structuredClone(m)),
|
|
31918
|
+
synthesizedResults: 0,
|
|
31919
|
+
droppedMessages: 0
|
|
31920
|
+
};
|
|
31921
|
+
}
|
|
31922
|
+
const synthetic = {
|
|
31923
|
+
role: "user",
|
|
31924
|
+
blocks: [...open4].map((callId) => ({
|
|
31925
|
+
t: "tool_result",
|
|
31926
|
+
callId,
|
|
31927
|
+
content: [{ t: "text", text: SYNTHETIC_TOOL_RESULT_TEXT }],
|
|
31928
|
+
isError: true
|
|
31929
|
+
}))
|
|
31930
|
+
};
|
|
31931
|
+
return {
|
|
31932
|
+
messages: [...messages.map((m) => structuredClone(m)), synthetic],
|
|
31933
|
+
synthesizedResults: open4.size,
|
|
31934
|
+
droppedMessages: 0
|
|
31935
|
+
};
|
|
31936
|
+
}
|
|
31937
|
+
|
|
31938
|
+
// src/session.ts
|
|
31365
31939
|
function createEventQueue() {
|
|
31366
31940
|
const buffered = [];
|
|
31367
31941
|
let ended = false;
|
|
@@ -31637,7 +32211,21 @@ var AgentSession = class {
|
|
|
31637
32211
|
};
|
|
31638
32212
|
async function createSession(options = {}) {
|
|
31639
32213
|
const cwd = options.cwd ?? process.cwd();
|
|
31640
|
-
|
|
32214
|
+
if (options.resume !== void 0) {
|
|
32215
|
+
if (options.initialMessages !== void 0) {
|
|
32216
|
+
throw new TansrSdkError(
|
|
32217
|
+
"invalid_options",
|
|
32218
|
+
'createSession: "resume" and "initialMessages" are mutually exclusive; "resume" loads the history from the store ("initialMessages" is the low-level re-feed escape hatch).'
|
|
32219
|
+
);
|
|
32220
|
+
}
|
|
32221
|
+
if (options.sessionId !== void 0 && options.sessionId !== options.resume.sessionId) {
|
|
32222
|
+
throw new TansrSdkError(
|
|
32223
|
+
"invalid_options",
|
|
32224
|
+
'createSession: "sessionId" conflicts with "resume.sessionId"; pass one of them (they must match).'
|
|
32225
|
+
);
|
|
32226
|
+
}
|
|
32227
|
+
}
|
|
32228
|
+
const sessionId = options.sessionId ?? options.resume?.sessionId ?? randomUUID11();
|
|
31641
32229
|
const injected = options.client !== void 0;
|
|
31642
32230
|
const tokenTier = options.token !== void 0;
|
|
31643
32231
|
if (injected && typeof options.model === "string") {
|
|
@@ -31653,6 +32241,18 @@ async function createSession(options = {}) {
|
|
|
31653
32241
|
);
|
|
31654
32242
|
}
|
|
31655
32243
|
validateTokenTierOptions("createSession", options);
|
|
32244
|
+
let initialMessages = options.initialMessages ?? [];
|
|
32245
|
+
if (options.resume !== void 0) {
|
|
32246
|
+
const record = await options.resume.store.get(options.resume.sessionId);
|
|
32247
|
+
if (record === null) {
|
|
32248
|
+
throw new TansrSdkError(
|
|
32249
|
+
"session_not_found",
|
|
32250
|
+
`createSession: session "${options.resume.sessionId}" was not found in the given store; list() the store or create a fresh session instead.`
|
|
32251
|
+
);
|
|
32252
|
+
}
|
|
32253
|
+
initialMessages = repairHistoryPairing(record.messages).messages;
|
|
32254
|
+
}
|
|
32255
|
+
const platformSessionId = tokenTier ? ulid() : void 0;
|
|
31656
32256
|
let sessionRef = null;
|
|
31657
32257
|
const emitBody = (body, source) => {
|
|
31658
32258
|
sessionRef?.pushBody(body, source);
|
|
@@ -31675,6 +32275,9 @@ async function createSession(options = {}) {
|
|
|
31675
32275
|
baseUrl: options.baseUrl,
|
|
31676
32276
|
...typeof options.model === "string" ? { model: options.model } : {},
|
|
31677
32277
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
32278
|
+
// 拍板⑤:本会话全部 exchange(含 setModel 热切换与 Task 子代理,同
|
|
32279
|
+
// registry 同缝)恒携同一枚平台会话 ULID——会话维归因/亲和自此在场
|
|
32280
|
+
...platformSessionId !== void 0 ? { sessionId: () => platformSessionId } : {},
|
|
31678
32281
|
onProviderSelected: (selection) => {
|
|
31679
32282
|
const body = providerSwitchedBody(selection);
|
|
31680
32283
|
if (body !== null) emitBody(body, "provider");
|
|
@@ -31691,7 +32294,9 @@ async function createSession(options = {}) {
|
|
|
31691
32294
|
platformContext = {
|
|
31692
32295
|
baseUrl: options.baseUrl,
|
|
31693
32296
|
token: options.token,
|
|
31694
|
-
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
|
|
32297
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
32298
|
+
// S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
|
|
32299
|
+
platformModels: assembled.platformModels
|
|
31695
32300
|
};
|
|
31696
32301
|
} else {
|
|
31697
32302
|
const assembled = await assembleManagedModel({
|
|
@@ -31747,6 +32352,26 @@ async function createSession(options = {}) {
|
|
|
31747
32352
|
}
|
|
31748
32353
|
);
|
|
31749
32354
|
}
|
|
32355
|
+
const store = options.store;
|
|
32356
|
+
if (store !== void 0) {
|
|
32357
|
+
await store.create({
|
|
32358
|
+
sessionId,
|
|
32359
|
+
...platformSessionId !== void 0 ? { platformSessionId } : {}
|
|
32360
|
+
});
|
|
32361
|
+
}
|
|
32362
|
+
const userCommit = options.onHistoryCommit;
|
|
32363
|
+
const onHistoryCommit = store === void 0 ? userCommit : async (history, meta) => {
|
|
32364
|
+
let storeError;
|
|
32365
|
+
try {
|
|
32366
|
+
await store.commit(sessionId, history, meta);
|
|
32367
|
+
} catch (err) {
|
|
32368
|
+
storeError = err;
|
|
32369
|
+
}
|
|
32370
|
+
await userCommit?.(history, meta);
|
|
32371
|
+
if (storeError !== void 0) {
|
|
32372
|
+
throw storeError instanceof Error ? storeError : new Error(String(storeError));
|
|
32373
|
+
}
|
|
32374
|
+
};
|
|
31750
32375
|
const session = new AgentSession({
|
|
31751
32376
|
binding,
|
|
31752
32377
|
executor: tooling.executor,
|
|
@@ -31757,9 +32382,9 @@ async function createSession(options = {}) {
|
|
|
31757
32382
|
maxTokens,
|
|
31758
32383
|
maxTurnsPerQuery: options.maxTurnsPerQuery ?? DEFAULT_MAX_TURNS,
|
|
31759
32384
|
compaction: options.compaction === false ? void 0 : options.compaction ?? {},
|
|
31760
|
-
initialMessages
|
|
32385
|
+
initialMessages,
|
|
31761
32386
|
registry,
|
|
31762
|
-
onHistoryCommit
|
|
32387
|
+
onHistoryCommit,
|
|
31763
32388
|
...tooling.onEnded !== void 0 ? { onEnded: tooling.onEnded } : {}
|
|
31764
32389
|
});
|
|
31765
32390
|
sessionRef = session;
|
|
@@ -31770,6 +32395,272 @@ async function createSession(options = {}) {
|
|
|
31770
32395
|
return session;
|
|
31771
32396
|
}
|
|
31772
32397
|
|
|
32398
|
+
// src/sessions/file-store.ts
|
|
32399
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
32400
|
+
import { mkdir as mkdir3, readFile as readFile5, readdir, rename as rename2, rm as rm3 } from "node:fs/promises";
|
|
32401
|
+
import path22 from "node:path";
|
|
32402
|
+
var MESSAGE_KINDS2 = /* @__PURE__ */ new Set([
|
|
32403
|
+
"user_prompt",
|
|
32404
|
+
"assistant_message",
|
|
32405
|
+
"tool_result"
|
|
32406
|
+
]);
|
|
32407
|
+
function journalKindOf(message) {
|
|
32408
|
+
if (message.role === "assistant") return "assistant_message";
|
|
32409
|
+
return message.blocks.some((b) => b.t === "tool_result") ? "tool_result" : "user_prompt";
|
|
32410
|
+
}
|
|
32411
|
+
var TITLE_MAX_CHARS = 64;
|
|
32412
|
+
function sessionTitleOf(messages) {
|
|
32413
|
+
for (const message of messages) {
|
|
32414
|
+
if (message.role !== "user") continue;
|
|
32415
|
+
const text = message.blocks.flatMap((b) => b.t === "text" ? [b.text] : []).join(" ").replace(/\s+/g, " ").trim();
|
|
32416
|
+
if (text === "") continue;
|
|
32417
|
+
return text.length > TITLE_MAX_CHARS ? `${text.slice(0, TITLE_MAX_CHARS)}…` : text;
|
|
32418
|
+
}
|
|
32419
|
+
return void 0;
|
|
32420
|
+
}
|
|
32421
|
+
async function readRawMeta(sessionDir) {
|
|
32422
|
+
try {
|
|
32423
|
+
const parsed = JSON.parse(await readFile5(metaFilePath(sessionDir), "utf8"));
|
|
32424
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
32425
|
+
return parsed;
|
|
32426
|
+
} catch {
|
|
32427
|
+
return null;
|
|
32428
|
+
}
|
|
32429
|
+
}
|
|
32430
|
+
function toRecordMeta(raw) {
|
|
32431
|
+
const createdAt = typeof raw["createdAt"] === "string" ? raw["createdAt"] : "";
|
|
32432
|
+
const updatedAt = typeof raw["updatedAt"] === "string" ? raw["updatedAt"] : createdAt;
|
|
32433
|
+
const record = {
|
|
32434
|
+
sessionId: typeof raw["sessionId"] === "string" ? raw["sessionId"] : "",
|
|
32435
|
+
createdAt,
|
|
32436
|
+
updatedAt
|
|
32437
|
+
};
|
|
32438
|
+
if (typeof raw["title"] === "string") record.title = raw["title"];
|
|
32439
|
+
if (typeof raw["platformSessionId"] === "string" && raw["platformSessionId"] !== "") {
|
|
32440
|
+
record.platformSessionId = raw["platformSessionId"];
|
|
32441
|
+
}
|
|
32442
|
+
const extra = raw["meta"];
|
|
32443
|
+
if (typeof extra === "object" && extra !== null && !Array.isArray(extra)) {
|
|
32444
|
+
record.meta = extra;
|
|
32445
|
+
}
|
|
32446
|
+
return record;
|
|
32447
|
+
}
|
|
32448
|
+
function corrupted(sessionId, detail, cause) {
|
|
32449
|
+
return new TansrSdkError(
|
|
32450
|
+
"session_store_corrupted",
|
|
32451
|
+
`Session store for "${sessionId}" is corrupted: ${detail}. Refusing to resume from damaged history (delete the session directory to start over).`,
|
|
32452
|
+
cause !== void 0 ? { cause } : void 0
|
|
32453
|
+
);
|
|
32454
|
+
}
|
|
32455
|
+
function createFileSessionStore(options) {
|
|
32456
|
+
const storageRoot = options.dir;
|
|
32457
|
+
const tails = /* @__PURE__ */ new Map();
|
|
32458
|
+
function enqueue(sessionId, op) {
|
|
32459
|
+
const tail = tails.get(sessionId) ?? Promise.resolve();
|
|
32460
|
+
const task = tail.then(op);
|
|
32461
|
+
tails.set(
|
|
32462
|
+
sessionId,
|
|
32463
|
+
task.then(
|
|
32464
|
+
() => void 0,
|
|
32465
|
+
() => void 0
|
|
32466
|
+
)
|
|
32467
|
+
);
|
|
32468
|
+
return task;
|
|
32469
|
+
}
|
|
32470
|
+
async function patchRawMeta(sessionDir, mutate) {
|
|
32471
|
+
const raw = await readRawMeta(sessionDir);
|
|
32472
|
+
if (raw === null) return null;
|
|
32473
|
+
if (mutate(raw)) {
|
|
32474
|
+
await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(raw, null, 2)}
|
|
32475
|
+
`);
|
|
32476
|
+
}
|
|
32477
|
+
return raw;
|
|
32478
|
+
}
|
|
32479
|
+
async function appendMessages(writer, sessionId, parentEventId, messages) {
|
|
32480
|
+
let parent = parentEventId;
|
|
32481
|
+
for (const message of messages) {
|
|
32482
|
+
const eventId = randomUUID12();
|
|
32483
|
+
await writer.append({
|
|
32484
|
+
eventId,
|
|
32485
|
+
sessionId,
|
|
32486
|
+
branchId: MAIN_BRANCH_ID,
|
|
32487
|
+
kind: journalKindOf(message),
|
|
32488
|
+
parentEventId: parent,
|
|
32489
|
+
payload: message,
|
|
32490
|
+
producer: "sdk",
|
|
32491
|
+
visibility: { ...Visibilities.normal }
|
|
32492
|
+
});
|
|
32493
|
+
parent = eventId;
|
|
32494
|
+
}
|
|
32495
|
+
}
|
|
32496
|
+
async function rotateVolume(sessionDir, sessionId, history) {
|
|
32497
|
+
const tempDir = path22.join(sessionDir, `.rotate-${process.pid}-${Date.now().toString(36)}`);
|
|
32498
|
+
await mkdir3(tempDir, { recursive: true });
|
|
32499
|
+
try {
|
|
32500
|
+
const writer = await JournalWriter.open(tempDir);
|
|
32501
|
+
try {
|
|
32502
|
+
await appendMessages(writer, sessionId, null, history);
|
|
32503
|
+
} finally {
|
|
32504
|
+
await writer.close();
|
|
32505
|
+
}
|
|
32506
|
+
const tempAttachments = attachmentsDirPath(tempDir);
|
|
32507
|
+
let names = [];
|
|
32508
|
+
try {
|
|
32509
|
+
names = await readdir(tempAttachments);
|
|
32510
|
+
} catch {
|
|
32511
|
+
names = [];
|
|
32512
|
+
}
|
|
32513
|
+
if (names.length > 0) {
|
|
32514
|
+
const target = attachmentsDirPath(sessionDir);
|
|
32515
|
+
await mkdir3(target, { recursive: true });
|
|
32516
|
+
for (const name of names) {
|
|
32517
|
+
await rename2(path22.join(tempAttachments, name), path22.join(target, name));
|
|
32518
|
+
}
|
|
32519
|
+
}
|
|
32520
|
+
await rename2(journalFilePath(tempDir), journalFilePath(sessionDir));
|
|
32521
|
+
} finally {
|
|
32522
|
+
await rm3(tempDir, { recursive: true, force: true }).catch(() => void 0);
|
|
32523
|
+
}
|
|
32524
|
+
}
|
|
32525
|
+
return {
|
|
32526
|
+
async create(init) {
|
|
32527
|
+
const sessionId = init.sessionId ?? randomUUID12();
|
|
32528
|
+
return enqueue(sessionId, async () => {
|
|
32529
|
+
const { sessionDir, created } = await ensureSessionDir({
|
|
32530
|
+
storageRoot,
|
|
32531
|
+
sessionId,
|
|
32532
|
+
cwd: process.cwd(),
|
|
32533
|
+
...init.title !== void 0 ? { extra: { title: init.title } } : {}
|
|
32534
|
+
});
|
|
32535
|
+
const raw = await patchRawMeta(sessionDir, (m) => {
|
|
32536
|
+
let changed = false;
|
|
32537
|
+
if (created && typeof m["updatedAt"] !== "string") {
|
|
32538
|
+
m["updatedAt"] = typeof m["createdAt"] === "string" ? m["createdAt"] : (/* @__PURE__ */ new Date()).toISOString();
|
|
32539
|
+
changed = true;
|
|
32540
|
+
}
|
|
32541
|
+
if (init.platformSessionId !== void 0 && m["platformSessionId"] !== init.platformSessionId) {
|
|
32542
|
+
m["platformSessionId"] = init.platformSessionId;
|
|
32543
|
+
changed = true;
|
|
32544
|
+
}
|
|
32545
|
+
if (init.title !== void 0 && m["title"] !== init.title) {
|
|
32546
|
+
m["title"] = init.title;
|
|
32547
|
+
changed = true;
|
|
32548
|
+
}
|
|
32549
|
+
if (init.meta !== void 0 && created) {
|
|
32550
|
+
m["meta"] = init.meta;
|
|
32551
|
+
changed = true;
|
|
32552
|
+
}
|
|
32553
|
+
return changed;
|
|
32554
|
+
});
|
|
32555
|
+
if (raw === null) throw corrupted(sessionId, "meta.json unreadable right after creation");
|
|
32556
|
+
return toRecordMeta(raw);
|
|
32557
|
+
});
|
|
32558
|
+
},
|
|
32559
|
+
async get(sessionId) {
|
|
32560
|
+
return enqueue(sessionId, async () => {
|
|
32561
|
+
const sessionDir = sessionDirPath(storageRoot, sessionId);
|
|
32562
|
+
const raw = await readRawMeta(sessionDir);
|
|
32563
|
+
if (raw === null) return null;
|
|
32564
|
+
const lock = await acquireSessionLock(sessionDir);
|
|
32565
|
+
try {
|
|
32566
|
+
let result;
|
|
32567
|
+
try {
|
|
32568
|
+
result = await readAll(sessionDir);
|
|
32569
|
+
} catch (err) {
|
|
32570
|
+
throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
|
|
32571
|
+
}
|
|
32572
|
+
if (result.integrityBreakAt !== void 0) {
|
|
32573
|
+
throw corrupted(
|
|
32574
|
+
sessionId,
|
|
32575
|
+
`journal hash chain broken at record index ${result.integrityBreakAt}`
|
|
32576
|
+
);
|
|
32577
|
+
}
|
|
32578
|
+
const state = rebuildState(result.records);
|
|
32579
|
+
if (state.warnings.length > 0) {
|
|
32580
|
+
throw corrupted(sessionId, state.warnings.join("; "));
|
|
32581
|
+
}
|
|
32582
|
+
return { meta: toRecordMeta(raw), messages: state.messages };
|
|
32583
|
+
} finally {
|
|
32584
|
+
await lock.release();
|
|
32585
|
+
}
|
|
32586
|
+
});
|
|
32587
|
+
},
|
|
32588
|
+
async list(filter) {
|
|
32589
|
+
const root = path22.join(storageRoot, "sessions");
|
|
32590
|
+
let entries;
|
|
32591
|
+
try {
|
|
32592
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
32593
|
+
} catch {
|
|
32594
|
+
return [];
|
|
32595
|
+
}
|
|
32596
|
+
const records = [];
|
|
32597
|
+
for (const entry of entries) {
|
|
32598
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
32599
|
+
const raw = await readRawMeta(path22.join(root, entry.name));
|
|
32600
|
+
if (raw === null || typeof raw["sessionId"] !== "string") continue;
|
|
32601
|
+
records.push(toRecordMeta(raw));
|
|
32602
|
+
}
|
|
32603
|
+
records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
32604
|
+
return filter?.limit !== void 0 ? records.slice(0, filter.limit) : records;
|
|
32605
|
+
},
|
|
32606
|
+
async commit(sessionId, history, meta) {
|
|
32607
|
+
const snapshot = history.map((m) => structuredClone(m));
|
|
32608
|
+
return enqueue(sessionId, async () => {
|
|
32609
|
+
const title = sessionTitleOf(snapshot);
|
|
32610
|
+
const { sessionDir } = await ensureSessionDir({
|
|
32611
|
+
storageRoot,
|
|
32612
|
+
sessionId,
|
|
32613
|
+
cwd: process.cwd(),
|
|
32614
|
+
...title !== void 0 ? { extra: { title } } : {}
|
|
32615
|
+
});
|
|
32616
|
+
const lock = await acquireSessionLock(sessionDir);
|
|
32617
|
+
try {
|
|
32618
|
+
let result;
|
|
32619
|
+
try {
|
|
32620
|
+
result = await readAll(sessionDir, { rehydrateImages: false });
|
|
32621
|
+
} catch (err) {
|
|
32622
|
+
throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
|
|
32623
|
+
}
|
|
32624
|
+
if (result.integrityBreakAt !== void 0) {
|
|
32625
|
+
throw corrupted(
|
|
32626
|
+
sessionId,
|
|
32627
|
+
`journal hash chain broken at record index ${result.integrityBreakAt}`
|
|
32628
|
+
);
|
|
32629
|
+
}
|
|
32630
|
+
const persisted = result.records.filter(
|
|
32631
|
+
(r) => r.branchId === MAIN_BRANCH_ID && MESSAGE_KINDS2.has(r.kind) && r.visibility.model
|
|
32632
|
+
).length;
|
|
32633
|
+
if (meta.rewritten || snapshot.length < persisted) {
|
|
32634
|
+
await rotateVolume(sessionDir, sessionId, snapshot);
|
|
32635
|
+
} else {
|
|
32636
|
+
const delta = snapshot.slice(persisted);
|
|
32637
|
+
if (delta.length > 0) {
|
|
32638
|
+
const writer = await JournalWriter.open(sessionDir);
|
|
32639
|
+
try {
|
|
32640
|
+
const last = result.records[result.records.length - 1];
|
|
32641
|
+
await appendMessages(writer, sessionId, last?.eventId ?? null, delta);
|
|
32642
|
+
} finally {
|
|
32643
|
+
await writer.close();
|
|
32644
|
+
}
|
|
32645
|
+
}
|
|
32646
|
+
}
|
|
32647
|
+
await patchRawMeta(sessionDir, (m) => {
|
|
32648
|
+
m["updatedAt"] = (/* @__PURE__ */ new Date()).toISOString();
|
|
32649
|
+
return true;
|
|
32650
|
+
});
|
|
32651
|
+
} finally {
|
|
32652
|
+
await lock.release();
|
|
32653
|
+
}
|
|
32654
|
+
});
|
|
32655
|
+
},
|
|
32656
|
+
async delete(sessionId) {
|
|
32657
|
+
return enqueue(sessionId, async () => {
|
|
32658
|
+
await rm3(sessionDirPath(storageRoot, sessionId), { recursive: true, force: true });
|
|
32659
|
+
});
|
|
32660
|
+
}
|
|
32661
|
+
};
|
|
32662
|
+
}
|
|
32663
|
+
|
|
31773
32664
|
// src/view/reducer.ts
|
|
31774
32665
|
var OUTPUT_TAIL_MAX_CHARS = 4e3;
|
|
31775
32666
|
function initialSessionViewState() {
|
|
@@ -32367,6 +33258,58 @@ function markSourceFailure(state, error) {
|
|
|
32367
33258
|
});
|
|
32368
33259
|
}
|
|
32369
33260
|
|
|
33261
|
+
// src/view/history.ts
|
|
33262
|
+
function viewStateFromHistory(messages) {
|
|
33263
|
+
const results = /* @__PURE__ */ new Map();
|
|
33264
|
+
for (const message of messages) {
|
|
33265
|
+
for (const block of message.blocks) {
|
|
33266
|
+
if (block.t === "tool_result") results.set(block.callId, block);
|
|
33267
|
+
}
|
|
33268
|
+
}
|
|
33269
|
+
const uiMessages = [];
|
|
33270
|
+
let nextSeq = 1;
|
|
33271
|
+
for (const message of messages) {
|
|
33272
|
+
const parts = [];
|
|
33273
|
+
for (const block of message.blocks) {
|
|
33274
|
+
switch (block.t) {
|
|
33275
|
+
case "text":
|
|
33276
|
+
if (block.text !== "") parts.push({ type: "text", text: block.text });
|
|
33277
|
+
break;
|
|
33278
|
+
case "thinking":
|
|
33279
|
+
if (block.text !== "") parts.push({ type: "thinking", text: block.text });
|
|
33280
|
+
break;
|
|
33281
|
+
case "tool_call": {
|
|
33282
|
+
const result = results.get(block.id);
|
|
33283
|
+
const resultText = result?.content.filter((item) => item.t === "text").map((item) => item.text ?? "").join("\n");
|
|
33284
|
+
const status = result === void 0 ? "aborted" : result.isError === true ? "failed" : "completed";
|
|
33285
|
+
const part = {
|
|
33286
|
+
type: "toolCall",
|
|
33287
|
+
id: block.id,
|
|
33288
|
+
name: block.name,
|
|
33289
|
+
// 键在场性对齐 Kotlin(JsonElement?):args 键在场(含显式 null)
|
|
33290
|
+
// 即透传,键缺席即不落
|
|
33291
|
+
...block.args !== void 0 ? { args: block.args } : {},
|
|
33292
|
+
status,
|
|
33293
|
+
...resultText !== void 0 && resultText !== "" ? { resultText } : {}
|
|
33294
|
+
};
|
|
33295
|
+
parts.push(part);
|
|
33296
|
+
break;
|
|
33297
|
+
}
|
|
33298
|
+
default:
|
|
33299
|
+
break;
|
|
33300
|
+
}
|
|
33301
|
+
}
|
|
33302
|
+
if (parts.length === 0) continue;
|
|
33303
|
+
uiMessages.push({ id: `msg-${nextSeq}`, role: message.role, parts });
|
|
33304
|
+
nextSeq += 1;
|
|
33305
|
+
}
|
|
33306
|
+
const initial = initialSessionViewState();
|
|
33307
|
+
return {
|
|
33308
|
+
view: { ...initial.view, messages: uiMessages },
|
|
33309
|
+
internal: { ...initial.internal, nextMessageSeq: nextSeq }
|
|
33310
|
+
};
|
|
33311
|
+
}
|
|
33312
|
+
|
|
32370
33313
|
// src/view/pump.ts
|
|
32371
33314
|
function resolveIterable(source) {
|
|
32372
33315
|
if (Symbol.asyncIterator in source) return source;
|
|
@@ -32600,6 +33543,7 @@ export {
|
|
|
32600
33543
|
ImageGenArgsSchema,
|
|
32601
33544
|
McpHost,
|
|
32602
33545
|
OUTPUT_TAIL_MAX_CHARS,
|
|
33546
|
+
PLATFORM_SEARCH_PROVIDER_NAME,
|
|
32603
33547
|
QueueChannel,
|
|
32604
33548
|
SequentialToolExecutor,
|
|
32605
33549
|
TANSR_PROVIDER_ID,
|
|
@@ -32608,8 +33552,6 @@ export {
|
|
|
32608
33552
|
UnavailableChannel,
|
|
32609
33553
|
VIDEOGEN_TOOL_MAX_DURATION,
|
|
32610
33554
|
VideoGenArgsSchema,
|
|
32611
|
-
WEBSEARCH_TOOL_MAX_RESULTS,
|
|
32612
|
-
WebSearchArgsSchema2 as WebSearchArgsSchema,
|
|
32613
33555
|
accumulateUsage,
|
|
32614
33556
|
appBundleRegistryConfig,
|
|
32615
33557
|
appendUserMessage,
|
|
@@ -32623,14 +33565,15 @@ export {
|
|
|
32623
33565
|
buildSdkToolSet,
|
|
32624
33566
|
buildToolGuideSegment,
|
|
32625
33567
|
createAppTokenFetch,
|
|
33568
|
+
createFileSessionStore,
|
|
32626
33569
|
createImageGenTool,
|
|
32627
33570
|
createMcpHost,
|
|
32628
33571
|
createNarrator,
|
|
33572
|
+
createPlatformSearchProvider,
|
|
32629
33573
|
createSdkPermissionGate,
|
|
32630
33574
|
createSession,
|
|
32631
33575
|
createSessionView,
|
|
32632
33576
|
createVideoGenTool,
|
|
32633
|
-
createWebSearchTool2 as createWebSearchTool,
|
|
32634
33577
|
defineSkill,
|
|
32635
33578
|
defineTool,
|
|
32636
33579
|
initialSessionViewState,
|
|
@@ -32644,5 +33587,6 @@ export {
|
|
|
32644
33587
|
resolvePlatformSelection,
|
|
32645
33588
|
runAgent,
|
|
32646
33589
|
subagentModelResolverOf,
|
|
32647
|
-
toToolDef
|
|
33590
|
+
toToolDef,
|
|
33591
|
+
viewStateFromHistory
|
|
32648
33592
|
};
|