@skydiveai/pi-extensions 0.1.256-beta.1 → 0.1.256-beta.2
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.mjs +1355 -100
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { DefaultExecutionEventBusManager, DefaultRequestHandler, InMemoryTaskStore } from "@a2a-js/sdk/server";
|
|
3
3
|
import { UserBuilder, restHandler } from "@a2a-js/sdk/server/express";
|
|
4
4
|
import { buildAgentCard, chainMiddleware, composeHandlers, createAgentExecutor, createProtocolHandlers, getCurrentTraceparent, logger, mountAt, requestHeaders, requestUrl, webHandlerToMiddleware } from "@skydiveai/pi-server";
|
|
5
|
-
import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { mkdir, mkdtemp, open, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { SettingsManager, createBashToolDefinition, createEditToolDefinition, createLocalBashOperations, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { z } from "zod";
|
|
@@ -24,12 +24,16 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
|
24
24
|
import { Resource } from "@opentelemetry/resources";
|
|
25
25
|
import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
26
26
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
27
|
-
import { execFile } from "node:child_process";
|
|
28
|
-
import { availableParallelism } from "node:os";
|
|
27
|
+
import { execFile, spawn } from "node:child_process";
|
|
28
|
+
import os, { availableParallelism } from "node:os";
|
|
29
29
|
import { promisify } from "node:util";
|
|
30
30
|
import { parse } from "yaml";
|
|
31
31
|
import { getModels } from "@earendil-works/pi-ai";
|
|
32
|
+
import { clampThinkingLevel, createAssistantMessageEventStream } from "@earendil-works/pi-ai-runtime";
|
|
33
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
32
34
|
import { createHash, randomUUID } from "node:crypto";
|
|
35
|
+
import { createSdkMcpServer, query, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
36
|
+
import { adjustMaxTokensForThinking, clampMaxTokensToContext } from "@earendil-works/pi-ai-runtime/api/simple-options";
|
|
33
37
|
//#region src/platform-env-middleware.ts
|
|
34
38
|
const ENVD_TOKEN_HEADER = "x-e2b-envd-token";
|
|
35
39
|
const ENVD_URL = "http://localhost:49983/envs";
|
|
@@ -260,7 +264,7 @@ function createHealthHandler({ metadata }) {
|
|
|
260
264
|
const CAPABILITY_SOUL_NUDGE = "New capability gained — once the current task is done, if this changes what you can do for the user, record it in `soul.md` so it carries into future conversations rather than being rediscovered from scratch (then commit and push).";
|
|
261
265
|
//#endregion
|
|
262
266
|
//#region src/extensions/tool-call-summary.ts
|
|
263
|
-
const log$
|
|
267
|
+
const log$19 = logger.child({ module: "tool-call-summary-extension" });
|
|
264
268
|
/**
|
|
265
269
|
* The injected parameter name. Deliberately markdown-safe: the previous wire
|
|
266
270
|
* name (`__skydive_summary__`, now `TOOL_CALL_SUMMARY_FIELD_LEGACY`) carried
|
|
@@ -494,7 +498,7 @@ function withSummaryStrippedBeforeValidation(tool) {
|
|
|
494
498
|
try {
|
|
495
499
|
stripped = withoutInjectedSummary(args);
|
|
496
500
|
} catch (err) {
|
|
497
|
-
log$
|
|
501
|
+
log$19.error({
|
|
498
502
|
err,
|
|
499
503
|
event: "tool_call_summary_prepare_strip_failed",
|
|
500
504
|
toolName: tool.name
|
|
@@ -525,7 +529,7 @@ function stripInjectedSummary(pi, toolName, input) {
|
|
|
525
529
|
if (toolDeclaresSummaryParam(pi, toolName)) return;
|
|
526
530
|
for (const field of TOOL_CALL_SUMMARY_FIELDS) delete input[field];
|
|
527
531
|
} catch (err) {
|
|
528
|
-
log$
|
|
532
|
+
log$19.error({
|
|
529
533
|
err,
|
|
530
534
|
event: "tool_call_summary_strip_failed",
|
|
531
535
|
toolName
|
|
@@ -541,7 +545,7 @@ function buildInjectedPayload(pi, payload) {
|
|
|
541
545
|
try {
|
|
542
546
|
return injectToolCallSummary(payload, getStrictToolNames(pi));
|
|
543
547
|
} catch (err) {
|
|
544
|
-
log$
|
|
548
|
+
log$19.error({
|
|
545
549
|
err,
|
|
546
550
|
event: "tool_call_summary_injection_failed"
|
|
547
551
|
}, "tool_call_summary injection failed; passing payload through unchanged");
|
|
@@ -560,7 +564,7 @@ const toolCallSummaryExtension = (pi) => {
|
|
|
560
564
|
pi.registerTool(withSummaryStrippedBeforeValidation(createEditToolDefinition(ctx.cwd)));
|
|
561
565
|
editOverrideRegistered = true;
|
|
562
566
|
} catch (err) {
|
|
563
|
-
log$
|
|
567
|
+
log$19.error({
|
|
564
568
|
err,
|
|
565
569
|
event: "tool_call_summary_edit_override_failed"
|
|
566
570
|
}, "failed to register summary-tolerant edit tool; built-in remains active");
|
|
@@ -591,7 +595,7 @@ const toolCallSummaryExtension = (pi) => {
|
|
|
591
595
|
* or `ToolDefinition[]`. Files starting with `_` or `.` are skipped, so
|
|
592
596
|
* `tools/_example.ts` documents the shape without registering.
|
|
593
597
|
*/
|
|
594
|
-
const log$
|
|
598
|
+
const log$18 = logger.child({ module: "local-tools-extension" });
|
|
595
599
|
const TOOLS_DIRNAME = "tools";
|
|
596
600
|
const fileState = /* @__PURE__ */ new Map();
|
|
597
601
|
let pendingLocalToolsUpdate = null;
|
|
@@ -735,7 +739,7 @@ async function reconcileAndQueue({ pi, dir, reason }) {
|
|
|
735
739
|
dir
|
|
736
740
|
});
|
|
737
741
|
if (reason !== "session_start" && summaryHasChanges$1(summary)) pendingLocalToolsUpdate = summary;
|
|
738
|
-
log$
|
|
742
|
+
log$18.info({
|
|
739
743
|
event: "local_tools_reconcile",
|
|
740
744
|
reason,
|
|
741
745
|
total_tools: summary.totalTools,
|
|
@@ -757,7 +761,7 @@ const localToolsExtension = (pi) => {
|
|
|
757
761
|
reason: "session_start"
|
|
758
762
|
});
|
|
759
763
|
} catch (err) {
|
|
760
|
-
log$
|
|
764
|
+
log$18.error({
|
|
761
765
|
err,
|
|
762
766
|
event: "local_tools_reconcile_failed"
|
|
763
767
|
}, "local tools reconcile failed");
|
|
@@ -769,7 +773,7 @@ const localToolsExtension = (pi) => {
|
|
|
769
773
|
try {
|
|
770
774
|
current = await listToolFiles(dir);
|
|
771
775
|
} catch (err) {
|
|
772
|
-
log$
|
|
776
|
+
log$18.warn({
|
|
773
777
|
err,
|
|
774
778
|
event: "local_tools_listing_failed"
|
|
775
779
|
}, "tools/ listing failed");
|
|
@@ -791,7 +795,7 @@ const localToolsExtension = (pi) => {
|
|
|
791
795
|
reason: "auto_reload"
|
|
792
796
|
});
|
|
793
797
|
} catch (err) {
|
|
794
|
-
log$
|
|
798
|
+
log$18.error({
|
|
795
799
|
err,
|
|
796
800
|
event: "local_tools_auto_reload_failed"
|
|
797
801
|
}, "auto-reload after tools/ change failed");
|
|
@@ -1329,6 +1333,135 @@ async function loadMcpConfig(path) {
|
|
|
1329
1333
|
return { servers };
|
|
1330
1334
|
}
|
|
1331
1335
|
//#endregion
|
|
1336
|
+
//#region src/extensions/mcp/schema-refs.ts
|
|
1337
|
+
const STRIPPED_KEYWORDS = new Set([
|
|
1338
|
+
"$defs",
|
|
1339
|
+
"definitions",
|
|
1340
|
+
"$schema",
|
|
1341
|
+
"$id"
|
|
1342
|
+
]);
|
|
1343
|
+
const NAME_MAP_KEYWORDS = new Set([
|
|
1344
|
+
"properties",
|
|
1345
|
+
"patternProperties",
|
|
1346
|
+
"dependentSchemas",
|
|
1347
|
+
"dependencies"
|
|
1348
|
+
]);
|
|
1349
|
+
function isRecord(value) {
|
|
1350
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1351
|
+
}
|
|
1352
|
+
/** RFC 6901 token decoding; null when the percent-escape is malformed. */
|
|
1353
|
+
function decodePointerToken(token) {
|
|
1354
|
+
try {
|
|
1355
|
+
return decodeURIComponent(token).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
1356
|
+
} catch {
|
|
1357
|
+
return null;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
function isLocalRef(ref) {
|
|
1361
|
+
return ref === "#" || ref.startsWith("#/");
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Resolve a local JSON pointer against the root schema. Steps through arrays
|
|
1365
|
+
* by index too: the MCP SDK emits refs like `#/properties/first/anyOf/0`
|
|
1366
|
+
* when a Zod schema is reused.
|
|
1367
|
+
*/
|
|
1368
|
+
function resolveLocalRef({ root, ref }) {
|
|
1369
|
+
const tokens = ref === "#" ? [] : ref.slice(2).split("/");
|
|
1370
|
+
let node = root;
|
|
1371
|
+
for (const rawToken of tokens) {
|
|
1372
|
+
const token = decodePointerToken(rawToken);
|
|
1373
|
+
if (token === null) return { error: `malformed $ref '${ref}'` };
|
|
1374
|
+
if (Array.isArray(node)) {
|
|
1375
|
+
const index = /^(0|[1-9]\d*)$/.test(token) ? Number(token) : -1;
|
|
1376
|
+
node = index >= 0 ? node[index] : void 0;
|
|
1377
|
+
} else if (isRecord(node)) node = node[token];
|
|
1378
|
+
else node = void 0;
|
|
1379
|
+
if (node === void 0) return { error: `unresolvable $ref '${ref}'` };
|
|
1380
|
+
}
|
|
1381
|
+
return isRecord(node) ? { node } : { error: `$ref '${ref}' does not point at a schema object` };
|
|
1382
|
+
}
|
|
1383
|
+
function toProviderSafeSchema(inputSchema) {
|
|
1384
|
+
if (!isRecord(inputSchema)) return {
|
|
1385
|
+
ok: false,
|
|
1386
|
+
reason: "inputSchema is not an object"
|
|
1387
|
+
};
|
|
1388
|
+
const root = inputSchema;
|
|
1389
|
+
let inlinedRefs = 0;
|
|
1390
|
+
let unsupported = null;
|
|
1391
|
+
const visitValue = ({ value, stack, inNameMap }) => {
|
|
1392
|
+
if (Array.isArray(value)) return value.map((item) => visitValue({
|
|
1393
|
+
value: item,
|
|
1394
|
+
stack,
|
|
1395
|
+
inNameMap: false
|
|
1396
|
+
}));
|
|
1397
|
+
if (!isRecord(value)) return value;
|
|
1398
|
+
return visitSchema({
|
|
1399
|
+
node: value,
|
|
1400
|
+
stack,
|
|
1401
|
+
inNameMap
|
|
1402
|
+
});
|
|
1403
|
+
};
|
|
1404
|
+
const visitSchema = ({ node, stack, inNameMap }) => {
|
|
1405
|
+
const ref = node.$ref;
|
|
1406
|
+
if (!inNameMap && typeof ref === "string") {
|
|
1407
|
+
if (!isLocalRef(ref)) {
|
|
1408
|
+
unsupported ??= `non-local $ref '${ref}'`;
|
|
1409
|
+
return node;
|
|
1410
|
+
}
|
|
1411
|
+
if (stack.includes(ref)) {
|
|
1412
|
+
unsupported ??= `cyclic $ref '${ref}'`;
|
|
1413
|
+
return node;
|
|
1414
|
+
}
|
|
1415
|
+
const resolved = resolveLocalRef({
|
|
1416
|
+
root,
|
|
1417
|
+
ref
|
|
1418
|
+
});
|
|
1419
|
+
if ("error" in resolved) {
|
|
1420
|
+
unsupported ??= resolved.error;
|
|
1421
|
+
return node;
|
|
1422
|
+
}
|
|
1423
|
+
inlinedRefs++;
|
|
1424
|
+
const { $ref: _ref, ...siblings } = node;
|
|
1425
|
+
return {
|
|
1426
|
+
...visitSchema({
|
|
1427
|
+
node: resolved.node,
|
|
1428
|
+
stack: [...stack, ref],
|
|
1429
|
+
inNameMap: false
|
|
1430
|
+
}),
|
|
1431
|
+
...visitSchema({
|
|
1432
|
+
node: siblings,
|
|
1433
|
+
stack,
|
|
1434
|
+
inNameMap: false
|
|
1435
|
+
})
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
const out = {};
|
|
1439
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1440
|
+
if (!inNameMap && STRIPPED_KEYWORDS.has(key)) continue;
|
|
1441
|
+
out[key] = visitValue({
|
|
1442
|
+
value,
|
|
1443
|
+
stack,
|
|
1444
|
+
inNameMap: !inNameMap && NAME_MAP_KEYWORDS.has(key)
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
return out;
|
|
1448
|
+
};
|
|
1449
|
+
const schema = visitSchema({
|
|
1450
|
+
node: root,
|
|
1451
|
+
stack: [],
|
|
1452
|
+
inNameMap: false
|
|
1453
|
+
});
|
|
1454
|
+
if (unsupported !== null) return {
|
|
1455
|
+
ok: false,
|
|
1456
|
+
reason: unsupported
|
|
1457
|
+
};
|
|
1458
|
+
return {
|
|
1459
|
+
ok: true,
|
|
1460
|
+
schema,
|
|
1461
|
+
inlinedRefs
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
//#endregion
|
|
1332
1465
|
//#region src/extensions/mcp/index.ts
|
|
1333
1466
|
/**
|
|
1334
1467
|
* MCP adapter as a pi extension.
|
|
@@ -1356,7 +1489,7 @@ async function loadMcpConfig(path) {
|
|
|
1356
1489
|
* Clients are keyed by JSON-stringified config and reused across
|
|
1357
1490
|
* reloads — only changed configs reconnect.
|
|
1358
1491
|
*/
|
|
1359
|
-
const log$
|
|
1492
|
+
const log$17 = logger.child({ module: "mcp-extension" });
|
|
1360
1493
|
async function closeConnected(connected) {
|
|
1361
1494
|
try {
|
|
1362
1495
|
await connected.client.close();
|
|
@@ -1493,6 +1626,45 @@ function buildToolPromptSnippet({ description, inputSchema, serverId }) {
|
|
|
1493
1626
|
const enriched = constraintHint.length > 0 ? `${description}${description.length > 0 ? "\n\n" : ""}${constraintHint}` : description;
|
|
1494
1627
|
return enriched.length > 0 ? enriched : `MCP tool from server "${serverId}".`;
|
|
1495
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Rewrite each tool's inputSchema into a self-contained one (local `$ref`s
|
|
1631
|
+
* inlined, `$defs` stripped — see schema-refs.ts) and set aside the tools
|
|
1632
|
+
* that cannot be made provider-safe. Skipping is deliberate: a strict
|
|
1633
|
+
* upstream rejects the WHOLE request over one bad tool schema, so
|
|
1634
|
+
* registering it would cost the agent every turn, not just that tool.
|
|
1635
|
+
*/
|
|
1636
|
+
function providerSafeTools({ serverId, tools }) {
|
|
1637
|
+
const safe = [];
|
|
1638
|
+
const skipped = [];
|
|
1639
|
+
for (const tool of tools) {
|
|
1640
|
+
const result = toProviderSafeSchema(tool.inputSchema);
|
|
1641
|
+
if (!result.ok) {
|
|
1642
|
+
log$17.warn({
|
|
1643
|
+
event: "mcp_tool_schema_unsupported",
|
|
1644
|
+
server_id: serverId,
|
|
1645
|
+
tool_name: tool.name,
|
|
1646
|
+
reason: result.reason
|
|
1647
|
+
}, "skipping MCP tool whose input schema cannot be made provider-safe");
|
|
1648
|
+
skipped.push({
|
|
1649
|
+
name: tool.name,
|
|
1650
|
+
reason: result.reason
|
|
1651
|
+
});
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
const inputSchema = {
|
|
1655
|
+
...result.schema,
|
|
1656
|
+
type: "object"
|
|
1657
|
+
};
|
|
1658
|
+
safe.push({
|
|
1659
|
+
...tool,
|
|
1660
|
+
inputSchema
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
return {
|
|
1664
|
+
tools: safe,
|
|
1665
|
+
skipped
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1496
1668
|
var McpExtension = class {
|
|
1497
1669
|
mcpClients = /* @__PURE__ */ new Map();
|
|
1498
1670
|
registeredMcpToolNames = /* @__PURE__ */ new Set();
|
|
@@ -1624,33 +1796,37 @@ var McpExtension = class {
|
|
|
1624
1796
|
else if (outcome.change === "refreshed") summary.refreshed.push(id);
|
|
1625
1797
|
}
|
|
1626
1798
|
const alloc = outcomes.filter((o) => Boolean(o.tools)).map((o) => {
|
|
1627
|
-
const
|
|
1799
|
+
const { tools: list, skipped } = providerSafeTools({
|
|
1800
|
+
serverId: o.id,
|
|
1801
|
+
tools: o.tools.list
|
|
1802
|
+
});
|
|
1628
1803
|
return {
|
|
1629
1804
|
id: o.id,
|
|
1630
1805
|
client: o.tools.client,
|
|
1631
|
-
list
|
|
1632
|
-
advertised,
|
|
1633
|
-
|
|
1806
|
+
list,
|
|
1807
|
+
advertised: o.tools.list.length,
|
|
1808
|
+
skipped,
|
|
1809
|
+
room: Math.min(list.length, limits.maxToolsPerServer),
|
|
1634
1810
|
registered: 0,
|
|
1635
1811
|
serverTokens: 0,
|
|
1636
1812
|
droppedReason: null
|
|
1637
1813
|
};
|
|
1638
1814
|
});
|
|
1639
|
-
const maxAdvertised = alloc.reduce((m, s) => Math.max(m, s.
|
|
1815
|
+
const maxAdvertised = alloc.reduce((m, s) => Math.max(m, s.list.length), 0);
|
|
1640
1816
|
outer: for (let i = 0; i < maxAdvertised; i++) for (const s of alloc) {
|
|
1641
1817
|
if (summary.totalTools >= limits.maxTotalTools) {
|
|
1642
|
-
for (const t of alloc) if (t.registered < t.
|
|
1818
|
+
for (const t of alloc) if (t.registered < t.list.length && t.droppedReason === null) t.droppedReason = "total";
|
|
1643
1819
|
break outer;
|
|
1644
1820
|
}
|
|
1645
1821
|
if (i >= s.room) {
|
|
1646
|
-
if (s.registered < s.
|
|
1822
|
+
if (s.registered < s.list.length && s.droppedReason === null) s.droppedReason = "per_server";
|
|
1647
1823
|
continue;
|
|
1648
1824
|
}
|
|
1649
1825
|
const tool = s.list[i];
|
|
1650
1826
|
if (tool === void 0) continue;
|
|
1651
1827
|
const cost = estimateToolTokens(tool);
|
|
1652
1828
|
if (summary.tokensUsed + cost > tokenBudget && summary.totalTools > 0) {
|
|
1653
|
-
for (const t of alloc) if (t.registered < t.
|
|
1829
|
+
for (const t of alloc) if (t.registered < t.list.length && t.droppedReason === null) t.droppedReason = "tokens";
|
|
1654
1830
|
break outer;
|
|
1655
1831
|
}
|
|
1656
1832
|
this.registerMcpTool({
|
|
@@ -1665,14 +1841,15 @@ var McpExtension = class {
|
|
|
1665
1841
|
summary.tokensUsed += cost;
|
|
1666
1842
|
}
|
|
1667
1843
|
for (const s of alloc) {
|
|
1668
|
-
const dropped = s.
|
|
1844
|
+
const dropped = s.list.length - s.registered;
|
|
1669
1845
|
const droppedReason = dropped > 0 ? s.droppedReason : null;
|
|
1670
1846
|
if (dropped > 0) summary.droppedTools += dropped;
|
|
1671
1847
|
summary.toolCounts[s.id] = {
|
|
1672
1848
|
advertised: s.advertised,
|
|
1673
1849
|
registered: s.registered,
|
|
1674
1850
|
tokens: s.serverTokens,
|
|
1675
|
-
droppedReason
|
|
1851
|
+
droppedReason,
|
|
1852
|
+
skipped: s.skipped
|
|
1676
1853
|
};
|
|
1677
1854
|
}
|
|
1678
1855
|
return summary;
|
|
@@ -1928,7 +2105,7 @@ var McpExtension = class {
|
|
|
1928
2105
|
});
|
|
1929
2106
|
this.lastConfigMtimeMs = await readConfigMtimeMs(configPath);
|
|
1930
2107
|
if (reason !== "session_start" && summaryHasChanges(summary)) this.pendingMcpUpdate = summary;
|
|
1931
|
-
log$
|
|
2108
|
+
log$17.info({
|
|
1932
2109
|
event: "mcp_reconcile",
|
|
1933
2110
|
reason,
|
|
1934
2111
|
total_tools: summary.totalTools,
|
|
@@ -1955,7 +2132,7 @@ var McpExtension = class {
|
|
|
1955
2132
|
contextWindow: ctx.model?.contextWindow
|
|
1956
2133
|
});
|
|
1957
2134
|
} catch (err) {
|
|
1958
|
-
log$
|
|
2135
|
+
log$17.error({
|
|
1959
2136
|
err,
|
|
1960
2137
|
event: "mcp_reconcile_failed"
|
|
1961
2138
|
}, "MCP reconcile failed");
|
|
@@ -1969,7 +2146,7 @@ var McpExtension = class {
|
|
|
1969
2146
|
for (const legacy of renamed) {
|
|
1970
2147
|
if (this.loggedLegacyToolNames.has(legacy)) continue;
|
|
1971
2148
|
this.loggedLegacyToolNames.add(legacy);
|
|
1972
|
-
log$
|
|
2149
|
+
log$17.info({
|
|
1973
2150
|
event: "mcp_tool_legacy_name",
|
|
1974
2151
|
legacy_name: legacy,
|
|
1975
2152
|
tool_name: this.legacyToolNames.get(legacy)
|
|
@@ -1983,7 +2160,7 @@ var McpExtension = class {
|
|
|
1983
2160
|
try {
|
|
1984
2161
|
mtime = await readConfigMtimeMs(configPath);
|
|
1985
2162
|
} catch (err) {
|
|
1986
|
-
log$
|
|
2163
|
+
log$17.warn({
|
|
1987
2164
|
err,
|
|
1988
2165
|
event: "mcp_mtime_check_failed"
|
|
1989
2166
|
}, "mtime check on mcp.config.json failed");
|
|
@@ -1998,7 +2175,7 @@ var McpExtension = class {
|
|
|
1998
2175
|
contextWindow: ctx.model?.contextWindow
|
|
1999
2176
|
});
|
|
2000
2177
|
} catch (err) {
|
|
2001
|
-
log$
|
|
2178
|
+
log$17.error({
|
|
2002
2179
|
err,
|
|
2003
2180
|
event: "mcp_auto_reload_failed"
|
|
2004
2181
|
}, "auto-reload after mcp.config.json change failed");
|
|
@@ -2113,10 +2290,24 @@ function truncationLines(summary) {
|
|
|
2113
2290
|
}
|
|
2114
2291
|
return lines;
|
|
2115
2292
|
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Lines naming tools that were left out because their input schema cannot be
|
|
2295
|
+
* sent to the model. Kept apart from `truncationLines`: this is not a budget
|
|
2296
|
+
* problem the agent can fix by pruning, the server has to publish a
|
|
2297
|
+
* self-contained schema.
|
|
2298
|
+
*/
|
|
2299
|
+
function skippedSchemaLines(summary) {
|
|
2300
|
+
const entries = Object.entries(summary.toolCounts).filter(([, count]) => count.skipped.length > 0);
|
|
2301
|
+
if (entries.length === 0) return [];
|
|
2302
|
+
const lines = [` WARNING: ${entries.reduce((n, [, count]) => n + count.skipped.length, 0)} MCP tool(s) were NOT registered because their input schema cannot be sent to the model (unsupported $ref). The server must publish a self-contained schema; there is nothing to fix in mcp.config.json.`];
|
|
2303
|
+
for (const [id, count] of entries) for (const { name, reason } of count.skipped) lines.push(` - ${id}/${name}: ${reason}`);
|
|
2304
|
+
return lines;
|
|
2305
|
+
}
|
|
2116
2306
|
function summaryText(summary) {
|
|
2117
2307
|
const lines = [];
|
|
2118
2308
|
lines.push(`MCP reconcile complete: ${summary.totalTools} tool(s) live.`);
|
|
2119
2309
|
lines.push(...truncationLines(summary));
|
|
2310
|
+
lines.push(...skippedSchemaLines(summary));
|
|
2120
2311
|
if (summary.added.length > 0) lines.push(` Added: ${summary.added.join(", ")}`);
|
|
2121
2312
|
if (summary.refreshed.length > 0) lines.push(` Refreshed: ${summary.refreshed.join(", ")}`);
|
|
2122
2313
|
if (summary.removed.length > 0) lines.push(` Removed: ${summary.removed.join(", ")}`);
|
|
@@ -2160,6 +2351,11 @@ function formatMcpUpdateMessage(summary) {
|
|
|
2160
2351
|
lines.push("");
|
|
2161
2352
|
lines.push(...truncationLines(summary));
|
|
2162
2353
|
}
|
|
2354
|
+
const skippedLines = skippedSchemaLines(summary);
|
|
2355
|
+
if (skippedLines.length > 0) {
|
|
2356
|
+
lines.push("");
|
|
2357
|
+
lines.push(...skippedLines);
|
|
2358
|
+
}
|
|
2163
2359
|
const pending = pendingAuthEntries(summary);
|
|
2164
2360
|
if (pending.length > 0) {
|
|
2165
2361
|
lines.push("");
|
|
@@ -2335,7 +2531,7 @@ const HEARTBEAT_THROTTLE_MS = 6e4;
|
|
|
2335
2531
|
const TOOL_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
2336
2532
|
const MAX_TOOL_HEARTBEATS = 1440 * 60 * 1e3 / TOOL_HEARTBEAT_INTERVAL_MS;
|
|
2337
2533
|
const DAEMON_URL = "http://localhost:38994";
|
|
2338
|
-
const log$
|
|
2534
|
+
const log$16 = logger.child({ module: "platform-ext" });
|
|
2339
2535
|
function sandboxClient() {
|
|
2340
2536
|
const apiUrl = apiBaseUrl();
|
|
2341
2537
|
if (!apiUrl) return null;
|
|
@@ -2403,7 +2599,7 @@ async function fetchHarnessFlags() {
|
|
|
2403
2599
|
try {
|
|
2404
2600
|
const res = await client["feature-flags"].$get();
|
|
2405
2601
|
if (!res.ok) {
|
|
2406
|
-
log$
|
|
2602
|
+
log$16.debug({
|
|
2407
2603
|
status: res.status,
|
|
2408
2604
|
event: "feature_flags_fetch_failed"
|
|
2409
2605
|
}, "feature-flags fetch failed");
|
|
@@ -2415,7 +2611,7 @@ async function fetchHarnessFlags() {
|
|
|
2415
2611
|
closingText: body.closingText ?? null
|
|
2416
2612
|
};
|
|
2417
2613
|
} catch (err) {
|
|
2418
|
-
log$
|
|
2614
|
+
log$16.debug({
|
|
2419
2615
|
err,
|
|
2420
2616
|
event: "feature_flags_fetch_error"
|
|
2421
2617
|
}, "feature-flags request errored");
|
|
@@ -2426,7 +2622,7 @@ function postHeartbeat({ messageId }) {
|
|
|
2426
2622
|
const client = sandboxClient();
|
|
2427
2623
|
if (!client) return;
|
|
2428
2624
|
client.heartbeat.$post({ json: { messageId } }).catch((err) => {
|
|
2429
|
-
log$
|
|
2625
|
+
log$16.debug({
|
|
2430
2626
|
err,
|
|
2431
2627
|
event: "heartbeat_failed"
|
|
2432
2628
|
}, "heartbeat failed");
|
|
@@ -2438,7 +2634,7 @@ async function resolveConversationFromApi(messageId) {
|
|
|
2438
2634
|
try {
|
|
2439
2635
|
const res = await client["message-conversation"].$get({ query: { messageId } });
|
|
2440
2636
|
if (!res.ok) {
|
|
2441
|
-
log$
|
|
2637
|
+
log$16.warn({
|
|
2442
2638
|
status: res.status,
|
|
2443
2639
|
messageId,
|
|
2444
2640
|
event: "resolve_conversation_failed"
|
|
@@ -2447,7 +2643,7 @@ async function resolveConversationFromApi(messageId) {
|
|
|
2447
2643
|
}
|
|
2448
2644
|
return (await res.json()).conversationId ?? null;
|
|
2449
2645
|
} catch (err) {
|
|
2450
|
-
log$
|
|
2646
|
+
log$16.warn({
|
|
2451
2647
|
err,
|
|
2452
2648
|
messageId,
|
|
2453
2649
|
event: "resolve_conversation_error"
|
|
@@ -2472,13 +2668,13 @@ async function putBackgroundTaskJournalSpec({ messageId, spec }) {
|
|
|
2472
2668
|
messageId,
|
|
2473
2669
|
spec
|
|
2474
2670
|
} });
|
|
2475
|
-
if (!res.ok) log$
|
|
2671
|
+
if (!res.ok) log$16.warn({
|
|
2476
2672
|
status: res.status,
|
|
2477
2673
|
taskId: spec.id,
|
|
2478
2674
|
event: "bg_journal_put_failed"
|
|
2479
2675
|
}, "bg-task journal PUT failed");
|
|
2480
2676
|
} catch (err) {
|
|
2481
|
-
log$
|
|
2677
|
+
log$16.warn({
|
|
2482
2678
|
err,
|
|
2483
2679
|
taskId: spec.id,
|
|
2484
2680
|
event: "bg_journal_put_failed"
|
|
@@ -2494,7 +2690,7 @@ async function deleteBackgroundTaskJournalSpec({ messageId, taskId }) {
|
|
|
2494
2690
|
taskId
|
|
2495
2691
|
} });
|
|
2496
2692
|
} catch (err) {
|
|
2497
|
-
log$
|
|
2693
|
+
log$16.debug({
|
|
2498
2694
|
err,
|
|
2499
2695
|
taskId,
|
|
2500
2696
|
event: "bg_journal_delete_failed"
|
|
@@ -2509,7 +2705,7 @@ async function listBackgroundTaskJournalSpecs({ messageId }) {
|
|
|
2509
2705
|
if (!res.ok) return [];
|
|
2510
2706
|
return (await res.json()).specs ?? [];
|
|
2511
2707
|
} catch (err) {
|
|
2512
|
-
log$
|
|
2708
|
+
log$16.debug({
|
|
2513
2709
|
err,
|
|
2514
2710
|
event: "bg_journal_list_failed"
|
|
2515
2711
|
}, "bg-task journal GET failed");
|
|
@@ -2523,7 +2719,7 @@ function postBackgroundTasksSnapshot({ messageId, tasks }) {
|
|
|
2523
2719
|
messageId,
|
|
2524
2720
|
tasks
|
|
2525
2721
|
} }).catch((err) => {
|
|
2526
|
-
log$
|
|
2722
|
+
log$16.debug({
|
|
2527
2723
|
err,
|
|
2528
2724
|
event: "bg_tasks_snapshot_failed"
|
|
2529
2725
|
}, "bg-tasks snapshot publish failed");
|
|
@@ -2621,7 +2817,7 @@ function createToolHeartbeat({ messageId }) {
|
|
|
2621
2817
|
}
|
|
2622
2818
|
heartbeatCount++;
|
|
2623
2819
|
if (heartbeatCount > MAX_TOOL_HEARTBEATS) {
|
|
2624
|
-
log$
|
|
2820
|
+
log$16.warn({
|
|
2625
2821
|
heartbeatCount,
|
|
2626
2822
|
activeToolCalls: [...activeToolCalls]
|
|
2627
2823
|
}, "tool heartbeat max reached, stopping");
|
|
@@ -2652,7 +2848,7 @@ function postToDaemon(path, body) {
|
|
|
2652
2848
|
headers: { "content-type": "application/json" },
|
|
2653
2849
|
body: JSON.stringify(body)
|
|
2654
2850
|
}).catch((err) => {
|
|
2655
|
-
log$
|
|
2851
|
+
log$16.debug({
|
|
2656
2852
|
err,
|
|
2657
2853
|
path,
|
|
2658
2854
|
event: "daemon_post_failed"
|
|
@@ -2661,7 +2857,7 @@ function postToDaemon(path, body) {
|
|
|
2661
2857
|
}
|
|
2662
2858
|
function createPlatformExtensions({ sessionId, channelContext }) {
|
|
2663
2859
|
return (pi) => {
|
|
2664
|
-
log$
|
|
2860
|
+
log$16.info({
|
|
2665
2861
|
sessionId,
|
|
2666
2862
|
hasChannelContext: Boolean(channelContext)
|
|
2667
2863
|
}, "platform extension initialized");
|
|
@@ -2710,7 +2906,7 @@ function createPlatformExtensions({ sessionId, channelContext }) {
|
|
|
2710
2906
|
});
|
|
2711
2907
|
});
|
|
2712
2908
|
pi.on("agent_end", () => {
|
|
2713
|
-
log$
|
|
2909
|
+
log$16.info({ sessionId }, "session ending");
|
|
2714
2910
|
postToDaemon("/session/end", { sessionId });
|
|
2715
2911
|
});
|
|
2716
2912
|
};
|
|
@@ -2846,7 +3042,7 @@ const toolCallEnvExtension = (pi) => {
|
|
|
2846
3042
|
* semantics; once promoted, the command inherits background-task lifecycle
|
|
2847
3043
|
* (its own controller) and the foreground kill timeout no longer applies.
|
|
2848
3044
|
*/
|
|
2849
|
-
const log$
|
|
3045
|
+
const log$15 = logger.child({ module: "background-tasks-ext" });
|
|
2850
3046
|
const ops = createLocalBashOperations();
|
|
2851
3047
|
const STEER_BACKGROUND_DEFAULT_SECONDS = 60;
|
|
2852
3048
|
const STEER_BACKGROUND_ENV_KEYS = ["BASH_STEER_BACKGROUND_SECONDS", "SKYDIVE_BASH_STEER_BACKGROUND_SECONDS"];
|
|
@@ -2861,7 +3057,7 @@ function steerBackgroundThresholdMs() {
|
|
|
2861
3057
|
if (raw === void 0 || raw.trim() === "") continue;
|
|
2862
3058
|
const parsed = Number(raw);
|
|
2863
3059
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
2864
|
-
log$
|
|
3060
|
+
log$15.warn({
|
|
2865
3061
|
envKey: key,
|
|
2866
3062
|
raw
|
|
2867
3063
|
}, "invalid BASH_STEER_BACKGROUND_SECONDS, using default");
|
|
@@ -2890,7 +3086,7 @@ async function notifyBashSteerArrival(args) {
|
|
|
2890
3086
|
try {
|
|
2891
3087
|
await listener();
|
|
2892
3088
|
} catch (err) {
|
|
2893
|
-
log$
|
|
3089
|
+
log$15.error({
|
|
2894
3090
|
err,
|
|
2895
3091
|
sessionId: args.sessionId
|
|
2896
3092
|
}, "bash steer promotion failed");
|
|
@@ -2985,14 +3181,14 @@ function watchdogTick(now) {
|
|
|
2985
3181
|
const overSize = meta.logBytes > MAX_LOG_BYTES;
|
|
2986
3182
|
if (!overRuntime && !overSize) continue;
|
|
2987
3183
|
const reason = overSize ? `output exceeded ${MAX_LOG_BYTES / (1024 * 1024)}MiB` : `exceeded ${KEEPALIVE_MAX_MS / 6e4}m max runtime`;
|
|
2988
|
-
log$
|
|
3184
|
+
log$15.warn({
|
|
2989
3185
|
taskId: meta.id,
|
|
2990
3186
|
reason
|
|
2991
3187
|
}, "watchdog killing bg task");
|
|
2992
3188
|
try {
|
|
2993
3189
|
killTask(meta, reason);
|
|
2994
3190
|
} catch (err) {
|
|
2995
|
-
log$
|
|
3191
|
+
log$15.warn({
|
|
2996
3192
|
err,
|
|
2997
3193
|
taskId: meta.id
|
|
2998
3194
|
}, "watchdog kill failed");
|
|
@@ -3051,7 +3247,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
|
|
|
3051
3247
|
const now = Date.now();
|
|
3052
3248
|
const promotable = [...foregroundBash.values()].filter((call) => now - call.startedAt >= thresholdMs);
|
|
3053
3249
|
if (promotable.length === 0) return;
|
|
3054
|
-
log$
|
|
3250
|
+
log$15.info({
|
|
3055
3251
|
event: "bash_steer_promotion",
|
|
3056
3252
|
count: promotable.length,
|
|
3057
3253
|
thresholdSeconds: Math.round(thresholdMs / 1e3),
|
|
@@ -3076,7 +3272,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
|
|
|
3076
3272
|
try {
|
|
3077
3273
|
await mkdir(tasksDir(), { recursive: true });
|
|
3078
3274
|
} catch (err) {
|
|
3079
|
-
log$
|
|
3275
|
+
log$15.error({ err }, "bg task adopt: mkdir failed");
|
|
3080
3276
|
}
|
|
3081
3277
|
const logStream = createWriteStream(logPath(id), { flags: "a" });
|
|
3082
3278
|
logStream.on("error", () => {});
|
|
@@ -3103,7 +3299,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
|
|
|
3103
3299
|
tasks.set(id, meta);
|
|
3104
3300
|
ensureWatchdog();
|
|
3105
3301
|
schedulePublishSnapshot();
|
|
3106
|
-
log$
|
|
3302
|
+
log$15.info({
|
|
3107
3303
|
taskId: id,
|
|
3108
3304
|
conversationId,
|
|
3109
3305
|
startedAt: args.startedAt
|
|
@@ -3256,7 +3452,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
|
|
|
3256
3452
|
try {
|
|
3257
3453
|
shellPath = SettingsManager.create(ctx.cwd, getAgentDir()).getShellPath();
|
|
3258
3454
|
} catch (err) {
|
|
3259
|
-
log$
|
|
3455
|
+
log$15.warn({
|
|
3260
3456
|
err,
|
|
3261
3457
|
cwd: ctx.cwd
|
|
3262
3458
|
}, "bash steer promotion: shell settings unavailable, using default shell");
|
|
@@ -3271,7 +3467,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
|
|
|
3271
3467
|
...definition,
|
|
3272
3468
|
description: `${definition.description}\n\nLong-running bash commands are automatically moved to a background task when a new message arrives while the command has already been running for at least a minute: you get the task id immediately, are woken on completion, and must not depend on the command's output until that wake arrives. Shorter commands finish in the foreground like they always have.`
|
|
3273
3469
|
});
|
|
3274
|
-
log$
|
|
3470
|
+
log$15.info({
|
|
3275
3471
|
event: "bash_steer_promotion_enabled",
|
|
3276
3472
|
thresholdSeconds: Math.round(thresholdMs / 1e3)
|
|
3277
3473
|
}, "supervised bash tool registered (steer-triggered backgrounding)");
|
|
@@ -3314,7 +3510,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3314
3510
|
triggerTurn: true,
|
|
3315
3511
|
deliverAs: "followUp"
|
|
3316
3512
|
});
|
|
3317
|
-
log$
|
|
3513
|
+
log$15.info({
|
|
3318
3514
|
taskId: meta.id,
|
|
3319
3515
|
conversationId
|
|
3320
3516
|
}, "bg task completion delivered live (followUp)");
|
|
@@ -3322,7 +3518,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3322
3518
|
}
|
|
3323
3519
|
if (meta.messageId) {
|
|
3324
3520
|
meta.notified = true;
|
|
3325
|
-
log$
|
|
3521
|
+
log$15.info({
|
|
3326
3522
|
taskId: meta.id,
|
|
3327
3523
|
messageId: meta.messageId
|
|
3328
3524
|
}, "posting idle bg-task-done wake");
|
|
@@ -3332,12 +3528,12 @@ or if you were explicitly waiting to report it.`,
|
|
|
3332
3528
|
content: message.content
|
|
3333
3529
|
}).catch((err) => {
|
|
3334
3530
|
meta.notified = false;
|
|
3335
|
-
log$
|
|
3531
|
+
log$15.warn({
|
|
3336
3532
|
err,
|
|
3337
3533
|
taskId: meta.id
|
|
3338
3534
|
}, "bg-task-done wake failed; will retry at next session_start");
|
|
3339
3535
|
});
|
|
3340
|
-
} else log$
|
|
3536
|
+
} else log$15.info({ taskId: meta.id }, "bg task completed idle with no origin message; deferring to next session_start");
|
|
3341
3537
|
}
|
|
3342
3538
|
const SNAPSHOT_TAIL_LINES = 40;
|
|
3343
3539
|
let lastPublishedSignature = null;
|
|
@@ -3374,7 +3570,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3374
3570
|
tasks: snapshot
|
|
3375
3571
|
});
|
|
3376
3572
|
} catch (err) {
|
|
3377
|
-
log$
|
|
3573
|
+
log$15.debug({
|
|
3378
3574
|
err,
|
|
3379
3575
|
event: "bg_tasks_snapshot_build_failed"
|
|
3380
3576
|
}, "building bg-tasks snapshot failed");
|
|
@@ -3417,7 +3613,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3417
3613
|
logStream.end();
|
|
3418
3614
|
await finished(logStream).catch(() => {});
|
|
3419
3615
|
meta.running = false;
|
|
3420
|
-
log$
|
|
3616
|
+
log$15.info({
|
|
3421
3617
|
taskId: meta.id,
|
|
3422
3618
|
exitCode: meta.exitCode
|
|
3423
3619
|
}, "bg task finished");
|
|
@@ -3434,14 +3630,14 @@ or if you were explicitly waiting to report it.`,
|
|
|
3434
3630
|
try {
|
|
3435
3631
|
await mkdir(tasksDir(), { recursive: true });
|
|
3436
3632
|
} catch (err) {
|
|
3437
|
-
log$
|
|
3633
|
+
log$15.error({
|
|
3438
3634
|
err,
|
|
3439
3635
|
taskId: id
|
|
3440
3636
|
}, "background task dir unavailable");
|
|
3441
3637
|
}
|
|
3442
3638
|
const logStream = createWriteStream(logPath(id), { flags: "a" });
|
|
3443
3639
|
logStream.on("error", (err) => {
|
|
3444
|
-
log$
|
|
3640
|
+
log$15.warn({
|
|
3445
3641
|
err,
|
|
3446
3642
|
taskId: id
|
|
3447
3643
|
}, "bg task log write failed");
|
|
@@ -3491,7 +3687,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3491
3687
|
}).catch((err) => {
|
|
3492
3688
|
if (!meta.controller.signal.aborted) {
|
|
3493
3689
|
meta.error = err instanceof Error ? err.message : String(err);
|
|
3494
|
-
log$
|
|
3690
|
+
log$15.warn({
|
|
3495
3691
|
err,
|
|
3496
3692
|
taskId: id
|
|
3497
3693
|
}, "bg task exec error");
|
|
@@ -3500,7 +3696,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3500
3696
|
await finishTask(meta, logStream);
|
|
3501
3697
|
});
|
|
3502
3698
|
ensureWatchdog();
|
|
3503
|
-
log$
|
|
3699
|
+
log$15.info({
|
|
3504
3700
|
taskId: id,
|
|
3505
3701
|
conversationId
|
|
3506
3702
|
}, "bg task started");
|
|
@@ -3519,7 +3715,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3519
3715
|
for (const spec of specs) {
|
|
3520
3716
|
const live = tasks.get(spec.id);
|
|
3521
3717
|
if (live && sameScope(live, scopeKey())) continue;
|
|
3522
|
-
log$
|
|
3718
|
+
log$15.info({
|
|
3523
3719
|
taskId: spec.id,
|
|
3524
3720
|
conversationId
|
|
3525
3721
|
}, "relaunching journaled resumable bg task after restart");
|
|
@@ -3537,7 +3733,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3537
3733
|
stream.write(`<resumed-after-restart>requeued and relaunched on a new sandbox after the previous one was drained; this is a fresh execution of the command from the start, not a continuation</resumed-after-restart>\n`);
|
|
3538
3734
|
stream.end();
|
|
3539
3735
|
} catch (err) {
|
|
3540
|
-
log$
|
|
3736
|
+
log$15.debug({
|
|
3541
3737
|
err,
|
|
3542
3738
|
taskId: meta.id
|
|
3543
3739
|
}, "resume banner write failed");
|
|
@@ -3557,7 +3753,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3557
3753
|
meta.notified = true;
|
|
3558
3754
|
pi.sendMessage(await taskDoneMessage(meta));
|
|
3559
3755
|
}
|
|
3560
|
-
if (unnotified.length > 0) log$
|
|
3756
|
+
if (unnotified.length > 0) log$15.info({
|
|
3561
3757
|
conversationId,
|
|
3562
3758
|
count: unnotified.length
|
|
3563
3759
|
}, "injected completed bg tasks at session_start");
|
|
@@ -3752,7 +3948,7 @@ or if you were explicitly waiting to report it.`,
|
|
|
3752
3948
|
* read on the hot path before every LLM call), it falls back to the default
|
|
3753
3949
|
* for that knob and logs once.
|
|
3754
3950
|
*/
|
|
3755
|
-
const log$
|
|
3951
|
+
const log$14 = logger.child({ module: "context-management-config" });
|
|
3756
3952
|
const DEFAULT_CONTEXT_MANAGEMENT_CONFIG = {
|
|
3757
3953
|
enabled: false,
|
|
3758
3954
|
perResultMaxBytes: 16 * 1024,
|
|
@@ -3797,7 +3993,7 @@ function resolveContextManagementConfig(env = process.env) {
|
|
|
3797
3993
|
clearAtLeastTokens: env.SKYDIVE_CTX_CLEAR_AT_LEAST_TOKENS
|
|
3798
3994
|
});
|
|
3799
3995
|
if (!parsed.success) {
|
|
3800
|
-
log$
|
|
3996
|
+
log$14.warn({
|
|
3801
3997
|
event: "context_management_config_invalid",
|
|
3802
3998
|
err: parsed.error
|
|
3803
3999
|
}, "falling back to default context-management config");
|
|
@@ -3881,7 +4077,7 @@ function hasFlagSource(env = process.env) {
|
|
|
3881
4077
|
* alive and an indeterminate result (no api url / transient failure) leaves the
|
|
3882
4078
|
* last-known values untouched so a blip can't silently flip behavior.
|
|
3883
4079
|
*/
|
|
3884
|
-
const log$
|
|
4080
|
+
const log$13 = logger.child({ module: "feature-flags-poll" });
|
|
3885
4081
|
const FLAG_POLL_INTERVAL_MS = 6e4;
|
|
3886
4082
|
let contextManagement = null;
|
|
3887
4083
|
let closingText = null;
|
|
@@ -3921,7 +4117,7 @@ function apply(name, next) {
|
|
|
3921
4117
|
if (next !== prev) for (const cb of subscribers[name]) try {
|
|
3922
4118
|
cb(next);
|
|
3923
4119
|
} catch (err) {
|
|
3924
|
-
log$
|
|
4120
|
+
log$13.warn({
|
|
3925
4121
|
err,
|
|
3926
4122
|
flag: name
|
|
3927
4123
|
}, "flag subscriber threw");
|
|
@@ -3934,7 +4130,7 @@ async function pollOnce() {
|
|
|
3934
4130
|
apply("contextManagement", flags.contextManagement ?? null);
|
|
3935
4131
|
apply("closingText", flags.closingText ?? null);
|
|
3936
4132
|
} catch (err) {
|
|
3937
|
-
log$
|
|
4133
|
+
log$13.debug({ err }, "feature-flag poll threw");
|
|
3938
4134
|
}
|
|
3939
4135
|
}
|
|
3940
4136
|
/**
|
|
@@ -4136,7 +4332,7 @@ const bashDefaultTimeoutExtension = (pi) => {
|
|
|
4136
4332
|
* turn over an observability feature.
|
|
4137
4333
|
*/
|
|
4138
4334
|
const execFileAsync = promisify(execFile);
|
|
4139
|
-
const log$
|
|
4335
|
+
const log$12 = logger.child({ module: "resource-pressure-warning" });
|
|
4140
4336
|
const POLL_INTERVAL_MS = 1e4;
|
|
4141
4337
|
function envOverride(name) {
|
|
4142
4338
|
for (const prefix of ["SKYDIVE_", "ANYONE_"]) {
|
|
@@ -4333,7 +4529,7 @@ const resourcePressureWarningExtension = (pi) => {
|
|
|
4333
4529
|
warnedDiskThisTurn = true;
|
|
4334
4530
|
}
|
|
4335
4531
|
if (trigger === null) return;
|
|
4336
|
-
log$
|
|
4532
|
+
log$12.warn({
|
|
4337
4533
|
trigger,
|
|
4338
4534
|
mem,
|
|
4339
4535
|
cpuPct,
|
|
@@ -4371,7 +4567,7 @@ const resourcePressureWarningExtension = (pi) => {
|
|
|
4371
4567
|
if (!timer) {
|
|
4372
4568
|
timer = setInterval(() => {
|
|
4373
4569
|
checkOnce().catch((err) => {
|
|
4374
|
-
log$
|
|
4570
|
+
log$12.error({ err }, "resource pressure check failed");
|
|
4375
4571
|
});
|
|
4376
4572
|
}, POLL_INTERVAL_MS);
|
|
4377
4573
|
timer.unref?.();
|
|
@@ -4387,7 +4583,7 @@ const resourcePressureWarningExtension = (pi) => {
|
|
|
4387
4583
|
};
|
|
4388
4584
|
//#endregion
|
|
4389
4585
|
//#region src/extensions/disk-guard.ts
|
|
4390
|
-
const log$
|
|
4586
|
+
const log$11 = logger.child({ module: "disk-guard" });
|
|
4391
4587
|
/**
|
|
4392
4588
|
* In-band bypass. The guard is a safety net, not a jail: when the agent knows
|
|
4393
4589
|
* a flagged command is genuinely safe (writing to a different mount, a tiny
|
|
@@ -4527,7 +4723,7 @@ const diskGuardExtension = (pi) => {
|
|
|
4527
4723
|
if (!isSpaceHungryCommand(command)) return;
|
|
4528
4724
|
const diskPct = await readDiskUsePct();
|
|
4529
4725
|
if (!shouldBlockForDisk(command, diskPct)) return;
|
|
4530
|
-
log$
|
|
4726
|
+
log$11.warn({
|
|
4531
4727
|
diskPct,
|
|
4532
4728
|
command: command.slice(0, 200)
|
|
4533
4729
|
}, "blocked space-hungry bash command on near-full disk");
|
|
@@ -4539,7 +4735,7 @@ const diskGuardExtension = (pi) => {
|
|
|
4539
4735
|
};
|
|
4540
4736
|
//#endregion
|
|
4541
4737
|
//#region src/extensions/noop-loop-guard.ts
|
|
4542
|
-
const log$
|
|
4738
|
+
const log$10 = logger.child({ module: "noop-loop-guard" });
|
|
4543
4739
|
/** Length of the rolling window that counts silence commands across streak resets. */
|
|
4544
4740
|
const SILENCE_WINDOW_MS = 300 * 1e3;
|
|
4545
4741
|
/** Bare shell no-op: `true` or `:` (case-insensitive). */
|
|
@@ -4691,7 +4887,7 @@ const noopLoopGuardExtension = (pi) => {
|
|
|
4691
4887
|
if (bypassed) return;
|
|
4692
4888
|
if (streak < 3 && windowCount < 8) return;
|
|
4693
4889
|
const aborted = streak >= 25 || windowCount >= 40;
|
|
4694
|
-
log$
|
|
4890
|
+
log$10.warn({
|
|
4695
4891
|
streak,
|
|
4696
4892
|
windowCount,
|
|
4697
4893
|
aborted,
|
|
@@ -4758,7 +4954,7 @@ const noopLoopGuardExtension = (pi) => {
|
|
|
4758
4954
|
* embedding a script).
|
|
4759
4955
|
*/
|
|
4760
4956
|
const execFileP = promisify(execFile);
|
|
4761
|
-
const log$
|
|
4957
|
+
const log$9 = logger.child({ module: "sleep-guard" });
|
|
4762
4958
|
/** How long the `platform automation create` call may take before we fall back to guidance. */
|
|
4763
4959
|
const SCHEDULE_TIMEOUT_MS = 15e3;
|
|
4764
4960
|
/** Title for the one-shot wakes this guard schedules. Visible in the Routines UI. */
|
|
@@ -5002,7 +5198,7 @@ async function scheduleSleepWake(seconds, ledger) {
|
|
|
5002
5198
|
], { timeout: SCHEDULE_TIMEOUT_MS });
|
|
5003
5199
|
return at;
|
|
5004
5200
|
} catch (err) {
|
|
5005
|
-
log$
|
|
5201
|
+
log$9.warn({ err }, "failed to auto-schedule sleep wake; falling back to guidance");
|
|
5006
5202
|
return null;
|
|
5007
5203
|
}
|
|
5008
5204
|
}
|
|
@@ -5023,7 +5219,7 @@ const sleepGuardExtension = (pi) => {
|
|
|
5023
5219
|
if (typeof command !== "string" || command.length === 0) return;
|
|
5024
5220
|
const violation = shouldBlockForSleep(command);
|
|
5025
5221
|
if (!violation) return;
|
|
5026
|
-
log$
|
|
5222
|
+
log$9.warn({
|
|
5027
5223
|
seconds: violation.seconds,
|
|
5028
5224
|
leading: violation.leading,
|
|
5029
5225
|
unboundedLoop: violation.unboundedLoop,
|
|
@@ -5032,7 +5228,7 @@ const sleepGuardExtension = (pi) => {
|
|
|
5032
5228
|
if (!violation.unboundedLoop && violation.seconds <= 86400 && isPureSleepCommand(blankQuotedRegions(command))) {
|
|
5033
5229
|
const at = await scheduleSleepWake(violation.seconds, wakeLedger);
|
|
5034
5230
|
if (at) {
|
|
5035
|
-
log$
|
|
5231
|
+
log$9.info({
|
|
5036
5232
|
at,
|
|
5037
5233
|
seconds: violation.seconds
|
|
5038
5234
|
}, "auto-scheduled sleep wake");
|
|
@@ -5140,7 +5336,7 @@ function transformContextMessages(messages, config, now) {
|
|
|
5140
5336
|
}
|
|
5141
5337
|
//#endregion
|
|
5142
5338
|
//#region src/extensions/context-management.ts
|
|
5143
|
-
const log$
|
|
5339
|
+
const log$8 = logger.child({ module: "context-management-extension" });
|
|
5144
5340
|
function isAnthropicMessagesPayload(payload) {
|
|
5145
5341
|
if (typeof payload !== "object" || payload === null) return false;
|
|
5146
5342
|
const candidate = payload;
|
|
@@ -5205,13 +5401,13 @@ function createContextManagementExtension() {
|
|
|
5205
5401
|
setContextManagementFlagOverride(getPolledFlag("contextManagement"));
|
|
5206
5402
|
onFlagChange("contextManagement", (enabled) => {
|
|
5207
5403
|
setContextManagementFlagOverride(enabled);
|
|
5208
|
-
log$
|
|
5404
|
+
log$8.info({
|
|
5209
5405
|
event: "context_management_flag_update",
|
|
5210
5406
|
enabled
|
|
5211
5407
|
}, "context-management flag updated from platform");
|
|
5212
5408
|
});
|
|
5213
5409
|
startFeatureFlagPoller();
|
|
5214
|
-
log$
|
|
5410
|
+
log$8.info({
|
|
5215
5411
|
event: "context_management_registered",
|
|
5216
5412
|
enabled: initial.enabled,
|
|
5217
5413
|
flagSource: hasFlagSource(),
|
|
@@ -5223,13 +5419,13 @@ function createContextManagementExtension() {
|
|
|
5223
5419
|
const { messages } = event;
|
|
5224
5420
|
try {
|
|
5225
5421
|
const result = transformContextIfEnabled(messages, getContextManagementConfig(), Date.now());
|
|
5226
|
-
if (result.stats && (result.stats.clearedResults > 0 || result.stats.trimmedResults > 0)) log$
|
|
5422
|
+
if (result.stats && (result.stats.clearedResults > 0 || result.stats.trimmedResults > 0)) log$8.info({
|
|
5227
5423
|
event: "context_management_applied",
|
|
5228
5424
|
...result.stats
|
|
5229
5425
|
}, "trimmed/cleared tool output before LLM call");
|
|
5230
5426
|
return { messages: result.messages };
|
|
5231
5427
|
} catch (err) {
|
|
5232
|
-
log$
|
|
5428
|
+
log$8.error({
|
|
5233
5429
|
err,
|
|
5234
5430
|
event: "context_management_transform_failed"
|
|
5235
5431
|
}, "context transform failed; passing messages through unchanged");
|
|
@@ -5241,7 +5437,7 @@ function createContextManagementExtension() {
|
|
|
5241
5437
|
}
|
|
5242
5438
|
//#endregion
|
|
5243
5439
|
//#region src/extensions/current-time.ts
|
|
5244
|
-
const log$
|
|
5440
|
+
const log$7 = logger.child({ module: "current-time-extension" });
|
|
5245
5441
|
const PI_DATE_LINE = /^Current date:.*$/m;
|
|
5246
5442
|
function formatCurrentTimeLine(now) {
|
|
5247
5443
|
return `Current date: ${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")} (${new Intl.DateTimeFormat("en-US", {
|
|
@@ -5254,7 +5450,7 @@ const currentTimeExtension = (pi) => {
|
|
|
5254
5450
|
const line = formatCurrentTimeLine(/* @__PURE__ */ new Date());
|
|
5255
5451
|
const base = event.systemPrompt;
|
|
5256
5452
|
if (PI_DATE_LINE.test(base)) {
|
|
5257
|
-
log$
|
|
5453
|
+
log$7.info({ event: "pi_date_line_present" }, "pi base prompt carries its own 'Current date:' line again; replacing it in place (pi prompt format may have changed)");
|
|
5258
5454
|
return { systemPrompt: base.replace(PI_DATE_LINE, line) };
|
|
5259
5455
|
}
|
|
5260
5456
|
return { systemPrompt: `${base}\n${line}` };
|
|
@@ -5642,7 +5838,7 @@ async function migrateMemory({ cwd }) {
|
|
|
5642
5838
|
}
|
|
5643
5839
|
//#endregion
|
|
5644
5840
|
//#region src/extensions/memory.ts
|
|
5645
|
-
const log$
|
|
5841
|
+
const log$6 = logger.child({ module: "memory-extension" });
|
|
5646
5842
|
/**
|
|
5647
5843
|
* The standing instructions for the memory system. Always injected (even with
|
|
5648
5844
|
* no `MEMORY.md`) so the agent knows it can persist notes and how. `users/` is
|
|
@@ -5679,13 +5875,13 @@ const memoryExtension = (pi) => {
|
|
|
5679
5875
|
pi.on("session_start", async (_event, ctx) => {
|
|
5680
5876
|
try {
|
|
5681
5877
|
const migrated = await migrateMemory({ cwd: ctx.cwd });
|
|
5682
|
-
if (migrated !== null) log$
|
|
5878
|
+
if (migrated !== null) log$6.info({
|
|
5683
5879
|
event: "memory_migrated",
|
|
5684
5880
|
from: migrated.from,
|
|
5685
5881
|
to: migrated.to
|
|
5686
5882
|
}, "migrated memory layout to current version");
|
|
5687
5883
|
} catch (err) {
|
|
5688
|
-
log$
|
|
5884
|
+
log$6.warn({
|
|
5689
5885
|
err,
|
|
5690
5886
|
event: "memory_migration_failed"
|
|
5691
5887
|
}, "memory migration failed; continuing with existing index");
|
|
@@ -5697,7 +5893,7 @@ const memoryExtension = (pi) => {
|
|
|
5697
5893
|
index
|
|
5698
5894
|
});
|
|
5699
5895
|
} catch (err) {
|
|
5700
|
-
log$
|
|
5896
|
+
log$6.warn({
|
|
5701
5897
|
err,
|
|
5702
5898
|
event: "memory_index_failed"
|
|
5703
5899
|
}, "memory index read failed; injecting instructions only");
|
|
@@ -5711,7 +5907,7 @@ const memoryExtension = (pi) => {
|
|
|
5711
5907
|
};
|
|
5712
5908
|
//#endregion
|
|
5713
5909
|
//#region src/extensions/platform-memory.ts
|
|
5714
|
-
const log$
|
|
5910
|
+
const log$5 = logger.child({ module: "platform-memory-extension" });
|
|
5715
5911
|
/**
|
|
5716
5912
|
* Resolve the human on this turn via the API, keyed by the message id.
|
|
5717
5913
|
* `/sandbox/channel-context` only returns a sender for a platform-known
|
|
@@ -5724,13 +5920,13 @@ const log$3 = logger.child({ module: "platform-memory-extension" });
|
|
|
5724
5920
|
async function resolveTurnUser(messageId) {
|
|
5725
5921
|
const client = sandboxClient();
|
|
5726
5922
|
if (!client) {
|
|
5727
|
-
log$
|
|
5923
|
+
log$5.debug({ event: "resolve_turn_user_no_api_url" }, "no API url in env; withholding user memory");
|
|
5728
5924
|
return null;
|
|
5729
5925
|
}
|
|
5730
5926
|
try {
|
|
5731
5927
|
const res = await client["channel-context"].$get({ query: { messageId } });
|
|
5732
5928
|
if (!res.ok) {
|
|
5733
|
-
log$
|
|
5929
|
+
log$5.warn({
|
|
5734
5930
|
event: "resolve_turn_user_failed",
|
|
5735
5931
|
status: res.status
|
|
5736
5932
|
}, "channel-context returned non-ok; withholding user memory");
|
|
@@ -5743,7 +5939,7 @@ async function resolveTurnUser(messageId) {
|
|
|
5743
5939
|
displayName: sender.displayName
|
|
5744
5940
|
};
|
|
5745
5941
|
} catch (err) {
|
|
5746
|
-
log$
|
|
5942
|
+
log$5.warn({
|
|
5747
5943
|
err,
|
|
5748
5944
|
event: "resolve_turn_user_failed"
|
|
5749
5945
|
}, "failed to resolve current user; withholding user memory");
|
|
@@ -5790,7 +5986,7 @@ function createPlatformMemoryExtension({ channelContext }) {
|
|
|
5790
5986
|
user
|
|
5791
5987
|
});
|
|
5792
5988
|
} catch (err) {
|
|
5793
|
-
log$
|
|
5989
|
+
log$5.warn({
|
|
5794
5990
|
err,
|
|
5795
5991
|
event: "user_memory_index_failed"
|
|
5796
5992
|
}, "user memory index build failed; skipping injection");
|
|
@@ -5821,6 +6017,1064 @@ const providerApiCompatExtension = (pi) => {
|
|
|
5821
6017
|
})) });
|
|
5822
6018
|
};
|
|
5823
6019
|
//#endregion
|
|
6020
|
+
//#region src/extensions/claude-seat-history.ts
|
|
6021
|
+
const imageType = z.enum([
|
|
6022
|
+
"image/jpeg",
|
|
6023
|
+
"image/png",
|
|
6024
|
+
"image/gif",
|
|
6025
|
+
"image/webp"
|
|
6026
|
+
]);
|
|
6027
|
+
function userContent(content) {
|
|
6028
|
+
if (typeof content === "string") return content;
|
|
6029
|
+
return content.map((block) => {
|
|
6030
|
+
if (block.type === "text") return {
|
|
6031
|
+
type: "text",
|
|
6032
|
+
text: block.text
|
|
6033
|
+
};
|
|
6034
|
+
return {
|
|
6035
|
+
type: "image",
|
|
6036
|
+
source: {
|
|
6037
|
+
type: "base64",
|
|
6038
|
+
media_type: imageType.parse(block.mimeType),
|
|
6039
|
+
data: block.data
|
|
6040
|
+
}
|
|
6041
|
+
};
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
/** Shared Anthropic message projection for SDK replay and provider hooks. */
|
|
6045
|
+
function seatHistoryMessage({ message, model, toolName }) {
|
|
6046
|
+
let converted;
|
|
6047
|
+
switch (message.role) {
|
|
6048
|
+
case "user":
|
|
6049
|
+
converted = {
|
|
6050
|
+
role: "user",
|
|
6051
|
+
content: userContent(message.content)
|
|
6052
|
+
};
|
|
6053
|
+
break;
|
|
6054
|
+
case "toolResult":
|
|
6055
|
+
converted = {
|
|
6056
|
+
role: "user",
|
|
6057
|
+
content: [{
|
|
6058
|
+
type: "tool_result",
|
|
6059
|
+
tool_use_id: message.toolCallId,
|
|
6060
|
+
content: userContent(message.content),
|
|
6061
|
+
is_error: message.isError
|
|
6062
|
+
}]
|
|
6063
|
+
};
|
|
6064
|
+
break;
|
|
6065
|
+
case "assistant": {
|
|
6066
|
+
const content = message.content.flatMap((block) => {
|
|
6067
|
+
switch (block.type) {
|
|
6068
|
+
case "text": return block.text ? [{
|
|
6069
|
+
type: "text",
|
|
6070
|
+
text: block.text
|
|
6071
|
+
}] : [];
|
|
6072
|
+
case "toolCall": return [{
|
|
6073
|
+
type: "tool_use",
|
|
6074
|
+
id: block.id,
|
|
6075
|
+
name: toolName(block.name),
|
|
6076
|
+
input: block.arguments
|
|
6077
|
+
}];
|
|
6078
|
+
case "thinking": return message.provider === model.provider && message.model === model.id && block.thinkingSignature ? [{
|
|
6079
|
+
type: "thinking",
|
|
6080
|
+
thinking: block.thinking,
|
|
6081
|
+
signature: block.thinkingSignature
|
|
6082
|
+
}] : [];
|
|
6083
|
+
default: throw new Error(`Unsupported history block: ${String(block)}`);
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
if (content.length === 0) return null;
|
|
6087
|
+
converted = {
|
|
6088
|
+
role: "assistant",
|
|
6089
|
+
content
|
|
6090
|
+
};
|
|
6091
|
+
break;
|
|
6092
|
+
}
|
|
6093
|
+
default: throw new Error(`Unsupported history message: ${String(message)}`);
|
|
6094
|
+
}
|
|
6095
|
+
return converted;
|
|
6096
|
+
}
|
|
6097
|
+
/** Keep history roles and content intact at the pinned Claude Code resume boundary. */
|
|
6098
|
+
async function prepareSeatHistory({ context, configDir, cwd, toolName, model }) {
|
|
6099
|
+
const messages = [...context.messages];
|
|
6100
|
+
const last = messages.at(-1);
|
|
6101
|
+
const prompt = last?.role === "user" ? userContent(last.content) : "Continue from the existing conversation and tool results.";
|
|
6102
|
+
if (last?.role === "user") messages.pop();
|
|
6103
|
+
if (messages.length === 0) return {
|
|
6104
|
+
prompt,
|
|
6105
|
+
resume: null
|
|
6106
|
+
};
|
|
6107
|
+
const sessionId = randomUUID();
|
|
6108
|
+
let parentUuid = null;
|
|
6109
|
+
const entries = messages.flatMap((message) => {
|
|
6110
|
+
const converted = seatHistoryMessage({
|
|
6111
|
+
message,
|
|
6112
|
+
model,
|
|
6113
|
+
toolName
|
|
6114
|
+
});
|
|
6115
|
+
if (!converted) return [];
|
|
6116
|
+
const uuid = randomUUID();
|
|
6117
|
+
const entry = {
|
|
6118
|
+
type: converted.role,
|
|
6119
|
+
uuid,
|
|
6120
|
+
parentUuid,
|
|
6121
|
+
sessionId,
|
|
6122
|
+
cwd,
|
|
6123
|
+
isSidechain: false,
|
|
6124
|
+
timestamp: new Date(message.timestamp).toISOString(),
|
|
6125
|
+
message: message.role === "assistant" ? {
|
|
6126
|
+
...converted,
|
|
6127
|
+
id: `msg_${uuid}`,
|
|
6128
|
+
type: "message",
|
|
6129
|
+
model: message.model,
|
|
6130
|
+
stop_reason: message.stopReason === "toolUse" ? "tool_use" : "end_turn",
|
|
6131
|
+
stop_sequence: null,
|
|
6132
|
+
usage: {
|
|
6133
|
+
input_tokens: 0,
|
|
6134
|
+
output_tokens: 0
|
|
6135
|
+
}
|
|
6136
|
+
} : converted
|
|
6137
|
+
};
|
|
6138
|
+
parentUuid = uuid;
|
|
6139
|
+
return [entry];
|
|
6140
|
+
});
|
|
6141
|
+
if (entries.length === 0) return {
|
|
6142
|
+
prompt,
|
|
6143
|
+
resume: null
|
|
6144
|
+
};
|
|
6145
|
+
const resume = join(configDir, "history.jsonl");
|
|
6146
|
+
await writeFile(resume, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, { mode: 384 });
|
|
6147
|
+
return {
|
|
6148
|
+
prompt,
|
|
6149
|
+
resume
|
|
6150
|
+
};
|
|
6151
|
+
}
|
|
6152
|
+
//#endregion
|
|
6153
|
+
//#region src/extensions/claude-seat-input.ts
|
|
6154
|
+
/** Resolve a send only after the SDK has consumed it, before releasing tool results. */
|
|
6155
|
+
var SeatInput = class {
|
|
6156
|
+
pending = [];
|
|
6157
|
+
deliveries = /* @__PURE__ */ new Set();
|
|
6158
|
+
wake = null;
|
|
6159
|
+
closed = false;
|
|
6160
|
+
send(message) {
|
|
6161
|
+
if (this.closed) throw new Error("Claude seat input is closed");
|
|
6162
|
+
return new Promise((resolve) => {
|
|
6163
|
+
const delivered = () => {
|
|
6164
|
+
this.deliveries.delete(delivered);
|
|
6165
|
+
resolve();
|
|
6166
|
+
};
|
|
6167
|
+
this.deliveries.add(delivered);
|
|
6168
|
+
this.pending.push({
|
|
6169
|
+
message,
|
|
6170
|
+
delivered
|
|
6171
|
+
});
|
|
6172
|
+
this.wake?.();
|
|
6173
|
+
});
|
|
6174
|
+
}
|
|
6175
|
+
close() {
|
|
6176
|
+
this.closed = true;
|
|
6177
|
+
this.pending.length = 0;
|
|
6178
|
+
for (const delivered of this.deliveries) delivered();
|
|
6179
|
+
this.wake?.();
|
|
6180
|
+
}
|
|
6181
|
+
async *messages(initial) {
|
|
6182
|
+
yield initial;
|
|
6183
|
+
while (!this.closed) {
|
|
6184
|
+
const item = this.pending.shift();
|
|
6185
|
+
if (item) try {
|
|
6186
|
+
yield item.message;
|
|
6187
|
+
} finally {
|
|
6188
|
+
item.delivered();
|
|
6189
|
+
}
|
|
6190
|
+
else {
|
|
6191
|
+
await new Promise((resolve) => {
|
|
6192
|
+
this.wake = resolve;
|
|
6193
|
+
});
|
|
6194
|
+
this.wake = null;
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
};
|
|
6199
|
+
//#endregion
|
|
6200
|
+
//#region src/extensions/claude-seat-usage.ts
|
|
6201
|
+
function mapUsage(usage) {
|
|
6202
|
+
const input = usage.input_tokens ?? 0;
|
|
6203
|
+
const output = usage.output_tokens ?? 0;
|
|
6204
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
6205
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
6206
|
+
return {
|
|
6207
|
+
input,
|
|
6208
|
+
output,
|
|
6209
|
+
cacheRead,
|
|
6210
|
+
cacheWrite,
|
|
6211
|
+
totalTokens: input + output + cacheRead + cacheWrite,
|
|
6212
|
+
cost: {
|
|
6213
|
+
input: 0,
|
|
6214
|
+
output: 0,
|
|
6215
|
+
cacheRead: 0,
|
|
6216
|
+
cacheWrite: 0,
|
|
6217
|
+
total: 0
|
|
6218
|
+
}
|
|
6219
|
+
};
|
|
6220
|
+
}
|
|
6221
|
+
/** Preserve nonnegative step usage while making inconsistent SDK totals observable. */
|
|
6222
|
+
function remainingSeatUsage({ total, reported, log, sessionId, model }) {
|
|
6223
|
+
const remaining = {
|
|
6224
|
+
input_tokens: total.input - reported.input,
|
|
6225
|
+
output_tokens: total.output - reported.output,
|
|
6226
|
+
cache_read_input_tokens: total.cacheRead - reported.cacheRead,
|
|
6227
|
+
cache_creation_input_tokens: total.cacheWrite - reported.cacheWrite
|
|
6228
|
+
};
|
|
6229
|
+
if (Object.values(remaining).some((count) => count < 0)) log.warn({
|
|
6230
|
+
event: "claude_seat_usage_drift",
|
|
6231
|
+
sessionId,
|
|
6232
|
+
model,
|
|
6233
|
+
remaining,
|
|
6234
|
+
reportedUsage: reported,
|
|
6235
|
+
sdkUsage: total
|
|
6236
|
+
}, "Claude seat SDK usage total is below reported steps");
|
|
6237
|
+
return mapUsage({
|
|
6238
|
+
input_tokens: Math.max(0, remaining.input_tokens),
|
|
6239
|
+
output_tokens: Math.max(0, remaining.output_tokens),
|
|
6240
|
+
cache_read_input_tokens: Math.max(0, remaining.cache_read_input_tokens),
|
|
6241
|
+
cache_creation_input_tokens: Math.max(0, remaining.cache_creation_input_tokens)
|
|
6242
|
+
});
|
|
6243
|
+
}
|
|
6244
|
+
//#endregion
|
|
6245
|
+
//#region src/extensions/claude-seat-reasoning.ts
|
|
6246
|
+
const effortSchema = z.enum([
|
|
6247
|
+
"low",
|
|
6248
|
+
"medium",
|
|
6249
|
+
"high",
|
|
6250
|
+
"xhigh",
|
|
6251
|
+
"max"
|
|
6252
|
+
]);
|
|
6253
|
+
/** Keep the SDK's defaults from overriding Pi's requested reasoning budget. */
|
|
6254
|
+
function seatReasoningOptions({ model, context, options }) {
|
|
6255
|
+
const level = clampThinkingLevel(model, options.reasoning ?? "off");
|
|
6256
|
+
let maxTokens = clampMaxTokensToContext(model, context, options.maxTokens ?? model.maxTokens);
|
|
6257
|
+
const sdk = { thinking: { type: "disabled" } };
|
|
6258
|
+
if (level === "off") return {
|
|
6259
|
+
...sdk,
|
|
6260
|
+
maxTokens
|
|
6261
|
+
};
|
|
6262
|
+
if (model.thinkingLevelMap) {
|
|
6263
|
+
sdk.thinking = { type: "adaptive" };
|
|
6264
|
+
sdk.effort = effortSchema.parse(model.thinkingLevelMap?.[level] ?? (level === "minimal" ? "low" : level));
|
|
6265
|
+
return {
|
|
6266
|
+
...sdk,
|
|
6267
|
+
maxTokens
|
|
6268
|
+
};
|
|
6269
|
+
}
|
|
6270
|
+
const adjusted = adjustMaxTokensForThinking(maxTokens, model.maxTokens, level, options.thinkingBudgets);
|
|
6271
|
+
maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);
|
|
6272
|
+
const budgetTokens = Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024));
|
|
6273
|
+
if (budgetTokens >= 1024) sdk.thinking = {
|
|
6274
|
+
type: "enabled",
|
|
6275
|
+
budgetTokens
|
|
6276
|
+
};
|
|
6277
|
+
return {
|
|
6278
|
+
...sdk,
|
|
6279
|
+
maxTokens
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
//#endregion
|
|
6283
|
+
//#region src/extensions/claude-seat-payload.ts
|
|
6284
|
+
const payloadSchema = z.strictObject({
|
|
6285
|
+
model: z.string(),
|
|
6286
|
+
messages: z.array(z.unknown()),
|
|
6287
|
+
tools: z.array(z.object({
|
|
6288
|
+
name: z.string(),
|
|
6289
|
+
description: z.string(),
|
|
6290
|
+
input_schema: z.record(z.string(), z.json())
|
|
6291
|
+
})),
|
|
6292
|
+
context_management: z.object({ edits: z.array(z.object({ exclude_tools: z.array(z.string()).optional() }).catchall(z.json())) }).catchall(z.json()).optional()
|
|
6293
|
+
});
|
|
6294
|
+
/**
|
|
6295
|
+
* Apply Pi's tool-schema and native context-edit hooks to the SDK inputs on
|
|
6296
|
+
* every model step, before MCP name mapping. This is an input projection;
|
|
6297
|
+
* Claude Code owns the wire payload. Unsupported rewrites fail explicitly.
|
|
6298
|
+
*/
|
|
6299
|
+
async function prepareSeatRequest({ model, context, options }) {
|
|
6300
|
+
const payload = {
|
|
6301
|
+
model: model.id,
|
|
6302
|
+
messages: structuredClone(context.messages.flatMap((message) => {
|
|
6303
|
+
const converted = seatHistoryMessage({
|
|
6304
|
+
message,
|
|
6305
|
+
model,
|
|
6306
|
+
toolName: (name) => name
|
|
6307
|
+
});
|
|
6308
|
+
return converted ? [converted] : [];
|
|
6309
|
+
})),
|
|
6310
|
+
tools: (context.tools ?? []).map((tool) => ({
|
|
6311
|
+
name: tool.name,
|
|
6312
|
+
description: tool.description,
|
|
6313
|
+
input_schema: structuredClone(tool.parameters)
|
|
6314
|
+
}))
|
|
6315
|
+
};
|
|
6316
|
+
const unchanged = JSON.stringify([payload.model, payload.messages]);
|
|
6317
|
+
const updated = await options.onPayload?.(payload, model);
|
|
6318
|
+
const transformed = payloadSchema.parse(updated === void 0 ? payload : updated);
|
|
6319
|
+
if (JSON.stringify([transformed.model, transformed.messages]) !== unchanged) throw new Error("Claude seat SDK request hooks support tool schemas and context_management only; use the context hook to edit history");
|
|
6320
|
+
return {
|
|
6321
|
+
model,
|
|
6322
|
+
options,
|
|
6323
|
+
context: {
|
|
6324
|
+
...context,
|
|
6325
|
+
tools: transformed.tools.map((tool) => ({
|
|
6326
|
+
name: tool.name,
|
|
6327
|
+
description: tool.description,
|
|
6328
|
+
parameters: Type.Unsafe(tool.input_schema)
|
|
6329
|
+
}))
|
|
6330
|
+
},
|
|
6331
|
+
contextManagement: transformed.context_management ?? null
|
|
6332
|
+
};
|
|
6333
|
+
}
|
|
6334
|
+
/** Native edits address the same MCP names as the SDK's outgoing tools. */
|
|
6335
|
+
function seatContextManagement({ contextManagement, toolName }) {
|
|
6336
|
+
if (!contextManagement) return null;
|
|
6337
|
+
return {
|
|
6338
|
+
...contextManagement,
|
|
6339
|
+
edits: contextManagement.edits.map((edit) => ({
|
|
6340
|
+
...edit,
|
|
6341
|
+
...edit.exclude_tools ? { exclude_tools: edit.exclude_tools.map(toolName) } : {}
|
|
6342
|
+
}))
|
|
6343
|
+
};
|
|
6344
|
+
}
|
|
6345
|
+
//#endregion
|
|
6346
|
+
//#region src/extensions/claude-seat-query.ts
|
|
6347
|
+
const SEAT_SDK_API = "claude-agent-sdk";
|
|
6348
|
+
/** Bound each wait without limiting the total duration of a progressing turn. */
|
|
6349
|
+
const SEAT_QUERY_TIMEOUTS = {
|
|
6350
|
+
message: 5 * 6e4,
|
|
6351
|
+
input: 3e4,
|
|
6352
|
+
tools: 30 * 6e4
|
|
6353
|
+
};
|
|
6354
|
+
function fingerprint(value) {
|
|
6355
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
6356
|
+
}
|
|
6357
|
+
function requestKey({ model, context, options, contextManagement }) {
|
|
6358
|
+
return fingerprint({
|
|
6359
|
+
api: model.api,
|
|
6360
|
+
provider: model.provider,
|
|
6361
|
+
id: model.id,
|
|
6362
|
+
baseUrl: model.baseUrl,
|
|
6363
|
+
modelHeaders: model.headers,
|
|
6364
|
+
apiKey: options.apiKey,
|
|
6365
|
+
headers: options.headers,
|
|
6366
|
+
maxTokens: options.maxTokens,
|
|
6367
|
+
reasoning: options.reasoning,
|
|
6368
|
+
thinkingBudgets: options.thinkingBudgets,
|
|
6369
|
+
modelReasoning: model.reasoning,
|
|
6370
|
+
thinkingLevelMap: model.thinkingLevelMap,
|
|
6371
|
+
modelMaxTokens: model.maxTokens,
|
|
6372
|
+
contextWindow: model.contextWindow,
|
|
6373
|
+
systemPrompt: context.systemPrompt,
|
|
6374
|
+
tools: context.tools,
|
|
6375
|
+
contextManagement
|
|
6376
|
+
});
|
|
6377
|
+
}
|
|
6378
|
+
const PI_TOOLS_SERVER = "custom-tools";
|
|
6379
|
+
const log$4 = logger.child({ module: "claude-seat-sdk" });
|
|
6380
|
+
const toolMetadata = z.object({ _meta: z.object({ "claudecode/toolUseId": z.string().min(1) }) });
|
|
6381
|
+
/** Preserve existing MCP names; only ordinary Pi tools need a server prefix. */
|
|
6382
|
+
function toolBindings(context) {
|
|
6383
|
+
const parsed = (context.tools ?? []).map((definition) => {
|
|
6384
|
+
const [, server, name] = /^mcp__(.+?)__(.+)$/.exec(definition.name) ?? [];
|
|
6385
|
+
return {
|
|
6386
|
+
definition,
|
|
6387
|
+
server: server ?? null,
|
|
6388
|
+
name: name ?? null
|
|
6389
|
+
};
|
|
6390
|
+
});
|
|
6391
|
+
const existingServers = new Set(parsed.map((binding) => binding.server));
|
|
6392
|
+
let localServer = PI_TOOLS_SERVER;
|
|
6393
|
+
for (let suffix = 1; existingServers.has(localServer); suffix++) localServer = `${PI_TOOLS_SERVER}-${suffix}`;
|
|
6394
|
+
return parsed.map(({ definition, server, name }) => {
|
|
6395
|
+
const serverName = server ?? localServer;
|
|
6396
|
+
const toolName = name ?? definition.name;
|
|
6397
|
+
return {
|
|
6398
|
+
definition,
|
|
6399
|
+
serverName,
|
|
6400
|
+
toolName,
|
|
6401
|
+
sdkName: `mcp__${serverName}__${toolName}`
|
|
6402
|
+
};
|
|
6403
|
+
});
|
|
6404
|
+
}
|
|
6405
|
+
function platformHeaders({ model, options, sessionId }) {
|
|
6406
|
+
const headers = new Headers(model.headers);
|
|
6407
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) if (value === null) headers.delete(name);
|
|
6408
|
+
else headers.set(name, value);
|
|
6409
|
+
if (!headers.has("x-skydive-session-id")) headers.set("x-skydive-session-id", sessionId);
|
|
6410
|
+
headers.set("x-skydive-served-via", SEAT_SDK_API);
|
|
6411
|
+
return [...headers].filter(([name]) => name.startsWith("x-skydive-") || name.startsWith("x-anyone-") || [
|
|
6412
|
+
"traceparent",
|
|
6413
|
+
"tracestate",
|
|
6414
|
+
"baggage"
|
|
6415
|
+
].includes(name)).map(([name, value]) => `${name}: ${value}`).join("\n");
|
|
6416
|
+
}
|
|
6417
|
+
/** One SDK query, including tool rendezvous and its cancellable output queue. */
|
|
6418
|
+
var SeatQuery = class {
|
|
6419
|
+
controller = new AbortController();
|
|
6420
|
+
tools = /* @__PURE__ */ new Map();
|
|
6421
|
+
messages = [];
|
|
6422
|
+
reportedUsage = mapUsage({});
|
|
6423
|
+
input = new SeatInput();
|
|
6424
|
+
deliveredUsers = /* @__PURE__ */ new Map();
|
|
6425
|
+
wake = null;
|
|
6426
|
+
closed = false;
|
|
6427
|
+
failure = null;
|
|
6428
|
+
signals = /* @__PURE__ */ new Map();
|
|
6429
|
+
key;
|
|
6430
|
+
deliveredHistory;
|
|
6431
|
+
pendingTools = [];
|
|
6432
|
+
deadline = null;
|
|
6433
|
+
terminateChild = null;
|
|
6434
|
+
constructor(request, timeouts = SEAT_QUERY_TIMEOUTS) {
|
|
6435
|
+
this.request = request;
|
|
6436
|
+
this.timeouts = timeouts;
|
|
6437
|
+
this.key = requestKey(request);
|
|
6438
|
+
const { context } = request;
|
|
6439
|
+
this.deliveredHistory = {
|
|
6440
|
+
length: context.messages.length,
|
|
6441
|
+
key: fingerprint(context.messages)
|
|
6442
|
+
};
|
|
6443
|
+
for (const message of context.messages) {
|
|
6444
|
+
if (message.role !== "user") continue;
|
|
6445
|
+
const key = fingerprint(message);
|
|
6446
|
+
this.deliveredUsers.set(key, (this.deliveredUsers.get(key) ?? 0) + 1);
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
/** Only the unclaimed continuation of this exact tool batch can reuse the query. */
|
|
6450
|
+
claimContinuation(request) {
|
|
6451
|
+
if (this.closed || this.controller.signal.aborted || this.pendingTools.length === 0) return false;
|
|
6452
|
+
const calls = request.context.messages.findLast((message) => message.role === "assistant")?.content.flatMap((block) => block.type === "toolCall" ? [block.id] : []) ?? [];
|
|
6453
|
+
if (calls.length !== this.pendingTools.length || !this.pendingTools.every((id) => calls.includes(id) && request.context.messages.some((message) => message.role === "toolResult" && message.toolCallId === id))) return false;
|
|
6454
|
+
if (this.key !== requestKey(request) || this.deliveredHistory.key !== fingerprint(request.context.messages.slice(0, this.deliveredHistory.length))) {
|
|
6455
|
+
this.abort();
|
|
6456
|
+
return false;
|
|
6457
|
+
}
|
|
6458
|
+
this.pendingTools = [];
|
|
6459
|
+
return true;
|
|
6460
|
+
}
|
|
6461
|
+
park(message) {
|
|
6462
|
+
this.pendingTools = message.content.flatMap((block) => block.type === "toolCall" ? [block.id] : []);
|
|
6463
|
+
for (const id of this.pendingTools) this.toolSlot(id);
|
|
6464
|
+
this.reportedUsage.input += message.usage.input;
|
|
6465
|
+
this.reportedUsage.output += message.usage.output;
|
|
6466
|
+
this.reportedUsage.cacheRead += message.usage.cacheRead;
|
|
6467
|
+
this.reportedUsage.cacheWrite += message.usage.cacheWrite;
|
|
6468
|
+
this.setDeadline("tools");
|
|
6469
|
+
}
|
|
6470
|
+
setDeadline(phase) {
|
|
6471
|
+
if (this.deadline) clearTimeout(this.deadline);
|
|
6472
|
+
if (this.controller.signal.aborted) return;
|
|
6473
|
+
this.deadline = setTimeout(() => {
|
|
6474
|
+
const err = /* @__PURE__ */ new Error(`Claude seat SDK timed out waiting for ${phase}`);
|
|
6475
|
+
err.name = "TimeoutError";
|
|
6476
|
+
log$4.error({
|
|
6477
|
+
err,
|
|
6478
|
+
phase
|
|
6479
|
+
}, "Claude seat SDK wait deadline exceeded");
|
|
6480
|
+
this.abort(err);
|
|
6481
|
+
}, this.timeouts[phase]);
|
|
6482
|
+
this.deadline.unref();
|
|
6483
|
+
}
|
|
6484
|
+
bindSignal(signal) {
|
|
6485
|
+
if (!signal || this.signals.has(signal)) return;
|
|
6486
|
+
const abort = () => this.abort();
|
|
6487
|
+
this.signals.set(signal, abort);
|
|
6488
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6489
|
+
if (signal.aborted) abort();
|
|
6490
|
+
}
|
|
6491
|
+
toolSlot(id) {
|
|
6492
|
+
let slot = this.tools.get(id);
|
|
6493
|
+
if (!slot) {
|
|
6494
|
+
slot = {
|
|
6495
|
+
result: null,
|
|
6496
|
+
resolve: null
|
|
6497
|
+
};
|
|
6498
|
+
this.tools.set(id, slot);
|
|
6499
|
+
}
|
|
6500
|
+
return slot;
|
|
6501
|
+
}
|
|
6502
|
+
async deliverResults(context) {
|
|
6503
|
+
this.deliveredHistory = {
|
|
6504
|
+
length: context.messages.length,
|
|
6505
|
+
key: fingerprint(context.messages)
|
|
6506
|
+
};
|
|
6507
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
6508
|
+
for (const message of context.messages) {
|
|
6509
|
+
if (message.role !== "user") continue;
|
|
6510
|
+
const key = fingerprint(message);
|
|
6511
|
+
const count = (occurrences.get(key) ?? 0) + 1;
|
|
6512
|
+
occurrences.set(key, count);
|
|
6513
|
+
if (count <= (this.deliveredUsers.get(key) ?? 0)) continue;
|
|
6514
|
+
this.setDeadline("input");
|
|
6515
|
+
await this.input.send({
|
|
6516
|
+
type: "user",
|
|
6517
|
+
message: {
|
|
6518
|
+
role: "user",
|
|
6519
|
+
content: userContent(message.content)
|
|
6520
|
+
},
|
|
6521
|
+
parent_tool_use_id: null,
|
|
6522
|
+
priority: "next"
|
|
6523
|
+
});
|
|
6524
|
+
this.controller.signal.throwIfAborted();
|
|
6525
|
+
this.deliveredUsers.set(key, count);
|
|
6526
|
+
}
|
|
6527
|
+
this.setDeadline("message");
|
|
6528
|
+
for (const message of context.messages) {
|
|
6529
|
+
if (message.role !== "toolResult") continue;
|
|
6530
|
+
const slot = this.tools.get(message.toolCallId);
|
|
6531
|
+
if (!slot || slot.result) continue;
|
|
6532
|
+
slot.result = {
|
|
6533
|
+
content: message.content,
|
|
6534
|
+
isError: message.isError
|
|
6535
|
+
};
|
|
6536
|
+
slot.resolve?.(slot.result);
|
|
6537
|
+
slot.resolve = null;
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
abort(reason = null) {
|
|
6541
|
+
if (this.deadline) clearTimeout(this.deadline);
|
|
6542
|
+
this.deadline = null;
|
|
6543
|
+
this.terminateChild?.();
|
|
6544
|
+
this.controller.abort(reason ?? void 0);
|
|
6545
|
+
this.input.close();
|
|
6546
|
+
for (const [signal, abort] of this.signals) signal.removeEventListener("abort", abort);
|
|
6547
|
+
this.signals.clear();
|
|
6548
|
+
for (const slot of this.tools.values()) {
|
|
6549
|
+
slot.resolve?.({
|
|
6550
|
+
content: [{
|
|
6551
|
+
type: "text",
|
|
6552
|
+
text: "run aborted"
|
|
6553
|
+
}],
|
|
6554
|
+
isError: true
|
|
6555
|
+
});
|
|
6556
|
+
slot.resolve = null;
|
|
6557
|
+
}
|
|
6558
|
+
this.wake?.();
|
|
6559
|
+
}
|
|
6560
|
+
async next() {
|
|
6561
|
+
while (true) {
|
|
6562
|
+
this.controller.signal.throwIfAborted();
|
|
6563
|
+
const message = this.messages.shift();
|
|
6564
|
+
if (message) return message;
|
|
6565
|
+
if (this.failure) throw this.failure;
|
|
6566
|
+
if (this.closed) throw new Error("Claude Code query closed without a result");
|
|
6567
|
+
this.setDeadline("message");
|
|
6568
|
+
await new Promise((resolve) => {
|
|
6569
|
+
this.wake = resolve;
|
|
6570
|
+
});
|
|
6571
|
+
this.wake = null;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
async run({ sessionId, cwd }) {
|
|
6575
|
+
const { model, context, options } = this.request;
|
|
6576
|
+
let configDir = null;
|
|
6577
|
+
try {
|
|
6578
|
+
if (!options.apiKey) throw new Error("Claude seat SDK requires a seat credential sentinel");
|
|
6579
|
+
this.controller.signal.throwIfAborted();
|
|
6580
|
+
configDir = await mkdtemp(path.join(os.tmpdir(), "claude-seat-sdk-"));
|
|
6581
|
+
this.controller.signal.throwIfAborted();
|
|
6582
|
+
const bindings = toolBindings(context);
|
|
6583
|
+
const servers = /* @__PURE__ */ new Map();
|
|
6584
|
+
for (const { definition, serverName, toolName } of bindings) {
|
|
6585
|
+
const schema = z.fromJSONSchema(z.record(z.string(), z.json()).parse(definition.parameters));
|
|
6586
|
+
if (!(schema instanceof z.ZodObject)) throw new Error(`Tool ${definition.name} must have an object schema`);
|
|
6587
|
+
const tool$1 = tool(toolName, definition.description, schema.shape, async (_args, extra) => {
|
|
6588
|
+
const parsed = toolMetadata.safeParse(extra);
|
|
6589
|
+
if (!parsed.success) throw new Error("Claude SDK MCP request is missing its tool-use ID");
|
|
6590
|
+
this.controller.signal.throwIfAborted();
|
|
6591
|
+
const slot = this.toolSlot(parsed.data._meta["claudecode/toolUseId"]);
|
|
6592
|
+
if (slot.result) return slot.result;
|
|
6593
|
+
return new Promise((resolve) => {
|
|
6594
|
+
slot.resolve = resolve;
|
|
6595
|
+
});
|
|
6596
|
+
});
|
|
6597
|
+
const serverTools = servers.get(serverName) ?? [];
|
|
6598
|
+
serverTools.push(tool$1);
|
|
6599
|
+
servers.set(serverName, serverTools);
|
|
6600
|
+
}
|
|
6601
|
+
const history = await prepareSeatHistory({
|
|
6602
|
+
context,
|
|
6603
|
+
configDir,
|
|
6604
|
+
cwd,
|
|
6605
|
+
model,
|
|
6606
|
+
toolName: (name) => {
|
|
6607
|
+
const binding = bindings.find((candidate) => candidate.definition.name === name);
|
|
6608
|
+
if (binding) return binding.sdkName;
|
|
6609
|
+
if (name.startsWith("mcp__")) return name;
|
|
6610
|
+
return `mcp__${bindings.find((candidate) => !candidate.definition.name.startsWith("mcp__"))?.serverName ?? PI_TOOLS_SERVER}__${name}`;
|
|
6611
|
+
}
|
|
6612
|
+
});
|
|
6613
|
+
const { maxTokens, ...reasoning } = seatReasoningOptions(this.request);
|
|
6614
|
+
const contextManagement = seatContextManagement({
|
|
6615
|
+
contextManagement: this.request.contextManagement,
|
|
6616
|
+
toolName: (name) => bindings.find((binding) => binding.definition.name === name)?.sdkName ?? name
|
|
6617
|
+
});
|
|
6618
|
+
const sdkQuery = query({
|
|
6619
|
+
prompt: this.input.messages({
|
|
6620
|
+
type: "user",
|
|
6621
|
+
message: {
|
|
6622
|
+
role: "user",
|
|
6623
|
+
content: history.prompt
|
|
6624
|
+
},
|
|
6625
|
+
parent_tool_use_id: null
|
|
6626
|
+
}),
|
|
6627
|
+
options: {
|
|
6628
|
+
model: model.id,
|
|
6629
|
+
...reasoning,
|
|
6630
|
+
...history.resume ? { resume: history.resume } : {},
|
|
6631
|
+
verbatimPrompts: true,
|
|
6632
|
+
systemPrompt: {
|
|
6633
|
+
type: "preset",
|
|
6634
|
+
preset: "claude_code",
|
|
6635
|
+
append: context.systemPrompt ?? ""
|
|
6636
|
+
},
|
|
6637
|
+
tools: [],
|
|
6638
|
+
mcpServers: Object.fromEntries([...servers].map(([name, tools]) => [name, createSdkMcpServer({
|
|
6639
|
+
name,
|
|
6640
|
+
tools,
|
|
6641
|
+
timeout: this.timeouts.tools + this.timeouts.message + this.timeouts.input
|
|
6642
|
+
})])),
|
|
6643
|
+
allowedTools: bindings.map((binding) => binding.sdkName),
|
|
6644
|
+
permissionMode: "default",
|
|
6645
|
+
settingSources: [],
|
|
6646
|
+
cwd,
|
|
6647
|
+
includePartialMessages: true,
|
|
6648
|
+
persistSession: false,
|
|
6649
|
+
abortController: this.controller,
|
|
6650
|
+
spawnClaudeCodeProcess: ({ command, args, cwd, env }) => {
|
|
6651
|
+
const child = spawn(command, args, {
|
|
6652
|
+
cwd,
|
|
6653
|
+
env,
|
|
6654
|
+
stdio: [
|
|
6655
|
+
"pipe",
|
|
6656
|
+
"pipe",
|
|
6657
|
+
"inherit"
|
|
6658
|
+
]
|
|
6659
|
+
});
|
|
6660
|
+
const abort = () => {
|
|
6661
|
+
child.kill("SIGKILL");
|
|
6662
|
+
};
|
|
6663
|
+
this.terminateChild = abort;
|
|
6664
|
+
this.controller.signal.addEventListener("abort", abort, { once: true });
|
|
6665
|
+
child.once("exit", () => {
|
|
6666
|
+
this.terminateChild = null;
|
|
6667
|
+
this.controller.signal.removeEventListener("abort", abort);
|
|
6668
|
+
});
|
|
6669
|
+
if (this.controller.signal.aborted) abort();
|
|
6670
|
+
return child;
|
|
6671
|
+
},
|
|
6672
|
+
env: buildStdioEnv({
|
|
6673
|
+
ANTHROPIC_AUTH_TOKEN: options.apiKey,
|
|
6674
|
+
ANTHROPIC_API_KEY: "",
|
|
6675
|
+
ANTHROPIC_BASE_URL: model.baseUrl,
|
|
6676
|
+
ANTHROPIC_CUSTOM_HEADERS: platformHeaders({
|
|
6677
|
+
model,
|
|
6678
|
+
options,
|
|
6679
|
+
sessionId
|
|
6680
|
+
}),
|
|
6681
|
+
CLAUDE_CONFIG_DIR: configDir,
|
|
6682
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
6683
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxTokens),
|
|
6684
|
+
CLAUDE_CODE_EXTRA_BODY: JSON.stringify(contextManagement ? { context_management: contextManagement } : {})
|
|
6685
|
+
})
|
|
6686
|
+
}
|
|
6687
|
+
});
|
|
6688
|
+
try {
|
|
6689
|
+
for await (const message of sdkQuery) {
|
|
6690
|
+
this.messages.push(message);
|
|
6691
|
+
this.wake?.();
|
|
6692
|
+
if (message.type === "result") break;
|
|
6693
|
+
}
|
|
6694
|
+
} finally {
|
|
6695
|
+
sdkQuery.close();
|
|
6696
|
+
}
|
|
6697
|
+
} catch (err) {
|
|
6698
|
+
this.failure = err;
|
|
6699
|
+
if (!this.controller.signal.aborted) log$4.error({
|
|
6700
|
+
err,
|
|
6701
|
+
sessionId
|
|
6702
|
+
}, "Claude seat SDK query failed");
|
|
6703
|
+
} finally {
|
|
6704
|
+
this.closed = true;
|
|
6705
|
+
if (this.deadline) clearTimeout(this.deadline);
|
|
6706
|
+
this.deadline = null;
|
|
6707
|
+
this.input.close();
|
|
6708
|
+
this.wake?.();
|
|
6709
|
+
if (configDir) try {
|
|
6710
|
+
await rm(configDir, {
|
|
6711
|
+
recursive: true,
|
|
6712
|
+
force: true
|
|
6713
|
+
});
|
|
6714
|
+
} catch (err) {
|
|
6715
|
+
log$4.error({
|
|
6716
|
+
err,
|
|
6717
|
+
sessionId
|
|
6718
|
+
}, "Could not remove Claude seat SDK config");
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
};
|
|
6723
|
+
//#endregion
|
|
6724
|
+
//#region src/extensions/claude-seat-response.ts
|
|
6725
|
+
const toolInput = z.record(z.string(), z.unknown());
|
|
6726
|
+
function piToolName(name, context) {
|
|
6727
|
+
const binding = toolBindings(context).find((binding) => binding.sdkName === name);
|
|
6728
|
+
if (!binding) throw new Error(`Claude requested a tool outside the Pi bridge: ${name}`);
|
|
6729
|
+
return binding.definition.name;
|
|
6730
|
+
}
|
|
6731
|
+
/** Translates SDK block messages and raw deltas into one Pi model response. */
|
|
6732
|
+
var ModelResponse = class {
|
|
6733
|
+
message;
|
|
6734
|
+
live = /* @__PURE__ */ new Map();
|
|
6735
|
+
streamed = /* @__PURE__ */ new Set();
|
|
6736
|
+
canonicalIndex = 0;
|
|
6737
|
+
stopReason = null;
|
|
6738
|
+
constructor(model, context, events) {
|
|
6739
|
+
this.context = context;
|
|
6740
|
+
this.events = events;
|
|
6741
|
+
this.message = {
|
|
6742
|
+
role: "assistant",
|
|
6743
|
+
content: [],
|
|
6744
|
+
api: model.api,
|
|
6745
|
+
provider: "anthropic",
|
|
6746
|
+
model: model.id,
|
|
6747
|
+
usage: mapUsage({}),
|
|
6748
|
+
stopReason: "stop",
|
|
6749
|
+
timestamp: Date.now()
|
|
6750
|
+
};
|
|
6751
|
+
events.push({
|
|
6752
|
+
type: "start",
|
|
6753
|
+
partial: this.message
|
|
6754
|
+
});
|
|
6755
|
+
}
|
|
6756
|
+
startBlock(block, index) {
|
|
6757
|
+
this.events.push({
|
|
6758
|
+
type: block.type === "toolCall" ? "toolcall_start" : `${block.type}_start`,
|
|
6759
|
+
contentIndex: index,
|
|
6760
|
+
partial: this.message
|
|
6761
|
+
});
|
|
6762
|
+
}
|
|
6763
|
+
endBlock(block, index) {
|
|
6764
|
+
const event = {
|
|
6765
|
+
contentIndex: index,
|
|
6766
|
+
partial: this.message
|
|
6767
|
+
};
|
|
6768
|
+
switch (block.type) {
|
|
6769
|
+
case "text":
|
|
6770
|
+
this.events.push({
|
|
6771
|
+
...event,
|
|
6772
|
+
type: "text_end",
|
|
6773
|
+
content: block.text
|
|
6774
|
+
});
|
|
6775
|
+
break;
|
|
6776
|
+
case "thinking":
|
|
6777
|
+
this.events.push({
|
|
6778
|
+
...event,
|
|
6779
|
+
type: "thinking_end",
|
|
6780
|
+
content: block.thinking
|
|
6781
|
+
});
|
|
6782
|
+
break;
|
|
6783
|
+
case "toolCall":
|
|
6784
|
+
this.events.push({
|
|
6785
|
+
...event,
|
|
6786
|
+
type: "toolcall_end",
|
|
6787
|
+
toolCall: block
|
|
6788
|
+
});
|
|
6789
|
+
break;
|
|
6790
|
+
default: throw new Error(`Unsupported block: ${String(block)}`);
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
emitBlock(block, index) {
|
|
6794
|
+
this.startBlock(block, index);
|
|
6795
|
+
this.events.push({
|
|
6796
|
+
type: block.type === "toolCall" ? "toolcall_delta" : `${block.type}_delta`,
|
|
6797
|
+
contentIndex: index,
|
|
6798
|
+
delta: block.type === "toolCall" ? JSON.stringify(block.arguments) : block.type === "text" ? block.text : block.thinking,
|
|
6799
|
+
partial: this.message
|
|
6800
|
+
});
|
|
6801
|
+
this.endBlock(block, index);
|
|
6802
|
+
}
|
|
6803
|
+
assistant(message) {
|
|
6804
|
+
if (message.parent_tool_use_id) return;
|
|
6805
|
+
if (message.error) throw new Error(`Claude SDK assistant error: ${message.error}`);
|
|
6806
|
+
for (const sdkBlock of message.message.content) {
|
|
6807
|
+
let block;
|
|
6808
|
+
switch (sdkBlock.type) {
|
|
6809
|
+
case "text":
|
|
6810
|
+
block = {
|
|
6811
|
+
type: "text",
|
|
6812
|
+
text: sdkBlock.text
|
|
6813
|
+
};
|
|
6814
|
+
break;
|
|
6815
|
+
case "thinking":
|
|
6816
|
+
block = {
|
|
6817
|
+
type: "thinking",
|
|
6818
|
+
thinking: sdkBlock.thinking,
|
|
6819
|
+
thinkingSignature: sdkBlock.signature
|
|
6820
|
+
};
|
|
6821
|
+
break;
|
|
6822
|
+
case "tool_use":
|
|
6823
|
+
block = {
|
|
6824
|
+
type: "toolCall",
|
|
6825
|
+
id: sdkBlock.id,
|
|
6826
|
+
name: piToolName(sdkBlock.name, this.context),
|
|
6827
|
+
arguments: toolInput.parse(sdkBlock.input)
|
|
6828
|
+
};
|
|
6829
|
+
break;
|
|
6830
|
+
default: continue;
|
|
6831
|
+
}
|
|
6832
|
+
const index = this.canonicalIndex++;
|
|
6833
|
+
this.message.content[index] = block;
|
|
6834
|
+
if (!this.streamed.has(index)) this.emitBlock(block, index);
|
|
6835
|
+
}
|
|
6836
|
+
this.message.usage = mapUsage(message.message.usage);
|
|
6837
|
+
this.stopReason = message.message.stop_reason;
|
|
6838
|
+
}
|
|
6839
|
+
partial(message) {
|
|
6840
|
+
if (message.parent_tool_use_id) return false;
|
|
6841
|
+
const event = message.event;
|
|
6842
|
+
const partial = this.message;
|
|
6843
|
+
switch (event.type) {
|
|
6844
|
+
case "message_start":
|
|
6845
|
+
this.message.usage = mapUsage(event.message.usage);
|
|
6846
|
+
break;
|
|
6847
|
+
case "content_block_start": {
|
|
6848
|
+
const index = partial.content.length;
|
|
6849
|
+
const sdkBlock = event.content_block;
|
|
6850
|
+
let block;
|
|
6851
|
+
if (sdkBlock.type === "text") block = {
|
|
6852
|
+
type: "text",
|
|
6853
|
+
text: sdkBlock.text
|
|
6854
|
+
};
|
|
6855
|
+
else if (sdkBlock.type === "thinking") block = {
|
|
6856
|
+
type: "thinking",
|
|
6857
|
+
thinking: sdkBlock.thinking
|
|
6858
|
+
};
|
|
6859
|
+
else if (sdkBlock.type === "tool_use") block = {
|
|
6860
|
+
type: "toolCall",
|
|
6861
|
+
id: sdkBlock.id,
|
|
6862
|
+
name: piToolName(sdkBlock.name, this.context),
|
|
6863
|
+
arguments: {}
|
|
6864
|
+
};
|
|
6865
|
+
else break;
|
|
6866
|
+
this.live.set(event.index, {
|
|
6867
|
+
index,
|
|
6868
|
+
json: ""
|
|
6869
|
+
});
|
|
6870
|
+
this.streamed.add(index);
|
|
6871
|
+
partial.content[index] = block;
|
|
6872
|
+
this.startBlock(block, index);
|
|
6873
|
+
break;
|
|
6874
|
+
}
|
|
6875
|
+
case "content_block_delta": {
|
|
6876
|
+
const live = this.live.get(event.index);
|
|
6877
|
+
if (!live) break;
|
|
6878
|
+
const block = partial.content[live.index];
|
|
6879
|
+
const delta = event.delta;
|
|
6880
|
+
if (delta.type === "text_delta" && block?.type === "text") {
|
|
6881
|
+
block.text += delta.text;
|
|
6882
|
+
this.events.push({
|
|
6883
|
+
type: "text_delta",
|
|
6884
|
+
contentIndex: live.index,
|
|
6885
|
+
delta: delta.text,
|
|
6886
|
+
partial
|
|
6887
|
+
});
|
|
6888
|
+
} else if (delta.type === "thinking_delta" && block?.type === "thinking") {
|
|
6889
|
+
block.thinking += delta.thinking;
|
|
6890
|
+
this.events.push({
|
|
6891
|
+
type: "thinking_delta",
|
|
6892
|
+
contentIndex: live.index,
|
|
6893
|
+
delta: delta.thinking,
|
|
6894
|
+
partial
|
|
6895
|
+
});
|
|
6896
|
+
} else if (delta.type === "input_json_delta" && block?.type === "toolCall") {
|
|
6897
|
+
live.json += delta.partial_json;
|
|
6898
|
+
this.events.push({
|
|
6899
|
+
type: "toolcall_delta",
|
|
6900
|
+
contentIndex: live.index,
|
|
6901
|
+
delta: delta.partial_json,
|
|
6902
|
+
partial
|
|
6903
|
+
});
|
|
6904
|
+
}
|
|
6905
|
+
break;
|
|
6906
|
+
}
|
|
6907
|
+
case "content_block_stop": {
|
|
6908
|
+
const live = this.live.get(event.index);
|
|
6909
|
+
if (!live) break;
|
|
6910
|
+
const block = partial.content[live.index];
|
|
6911
|
+
if (block?.type === "toolCall" && live.json) block.arguments = toolInput.parse(JSON.parse(live.json));
|
|
6912
|
+
if (block) this.endBlock(block, live.index);
|
|
6913
|
+
this.live.delete(event.index);
|
|
6914
|
+
break;
|
|
6915
|
+
}
|
|
6916
|
+
case "message_delta":
|
|
6917
|
+
this.stopReason = event.delta.stop_reason;
|
|
6918
|
+
this.message.usage = mapUsage({
|
|
6919
|
+
input_tokens: event.usage.input_tokens ?? this.message.usage.input,
|
|
6920
|
+
output_tokens: event.usage.output_tokens,
|
|
6921
|
+
cache_read_input_tokens: event.usage.cache_read_input_tokens ?? this.message.usage.cacheRead,
|
|
6922
|
+
cache_creation_input_tokens: event.usage.cache_creation_input_tokens ?? this.message.usage.cacheWrite
|
|
6923
|
+
});
|
|
6924
|
+
break;
|
|
6925
|
+
case "message_stop": return true;
|
|
6926
|
+
}
|
|
6927
|
+
return false;
|
|
6928
|
+
}
|
|
6929
|
+
done(reason) {
|
|
6930
|
+
const stopReason = this.stopReason === "max_tokens" ? "length" : reason;
|
|
6931
|
+
this.message.stopReason = stopReason;
|
|
6932
|
+
this.events.push({
|
|
6933
|
+
type: "done",
|
|
6934
|
+
reason: stopReason,
|
|
6935
|
+
message: this.message
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
};
|
|
6939
|
+
//#endregion
|
|
6940
|
+
//#region src/extensions/claude-seat-sdk.ts
|
|
6941
|
+
/**
|
|
6942
|
+
* Adapts a Claude Code query to Pi's model-step interface. A query stays alive
|
|
6943
|
+
* across tool rounds, but Pi executes every tool. Results are forwarded from
|
|
6944
|
+
* the next model context, after all Pi tool_result extensions have run.
|
|
6945
|
+
* Each user turn resumes a private structured snapshot of the Pi transcript.
|
|
6946
|
+
* Initially adapted from elidickinson/pi-claude-bridge (MIT).
|
|
6947
|
+
*/
|
|
6948
|
+
const log$3 = logger.child({ module: "claude-seat-sdk" });
|
|
6949
|
+
/** Session-scoped adapter; only the flagged provider dispatches here. */
|
|
6950
|
+
var SeatSdkBridge = class {
|
|
6951
|
+
queries = /* @__PURE__ */ new Set();
|
|
6952
|
+
controller = new AbortController();
|
|
6953
|
+
constructor(deps, timeouts = SEAT_QUERY_TIMEOUTS) {
|
|
6954
|
+
this.deps = deps;
|
|
6955
|
+
this.timeouts = timeouts;
|
|
6956
|
+
}
|
|
6957
|
+
abort() {
|
|
6958
|
+
this.controller.abort();
|
|
6959
|
+
this.controller = new AbortController();
|
|
6960
|
+
for (const query of this.queries) query.abort();
|
|
6961
|
+
}
|
|
6962
|
+
stream(model, context, options = {}) {
|
|
6963
|
+
const events = createAssistantMessageEventStream();
|
|
6964
|
+
const response = new ModelResponse(model, context, events);
|
|
6965
|
+
const signal = AbortSignal.any([this.controller.signal, ...options.signal ? [options.signal] : []]);
|
|
6966
|
+
(async () => {
|
|
6967
|
+
let seatQuery = null;
|
|
6968
|
+
let keepQuery = false;
|
|
6969
|
+
try {
|
|
6970
|
+
const preparation = new AbortController();
|
|
6971
|
+
const request = await Promise.race([prepareSeatRequest({
|
|
6972
|
+
model,
|
|
6973
|
+
context,
|
|
6974
|
+
options
|
|
6975
|
+
}), setTimeout$1(this.timeouts.input, void 0, {
|
|
6976
|
+
signal: AbortSignal.any([signal, preparation.signal]),
|
|
6977
|
+
ref: false
|
|
6978
|
+
}).then(() => {
|
|
6979
|
+
const err = /* @__PURE__ */ new Error("Claude seat SDK timed out preparing request");
|
|
6980
|
+
err.name = "TimeoutError";
|
|
6981
|
+
throw err;
|
|
6982
|
+
})]).finally(() => preparation.abort());
|
|
6983
|
+
signal.throwIfAborted();
|
|
6984
|
+
const continuation = [...this.queries].find((query) => query.claimContinuation(request));
|
|
6985
|
+
const query = continuation ?? new SeatQuery(request, this.timeouts);
|
|
6986
|
+
seatQuery = query;
|
|
6987
|
+
query.bindSignal(signal);
|
|
6988
|
+
if (!continuation) {
|
|
6989
|
+
this.queries.add(query);
|
|
6990
|
+
query.run(this.deps).finally(() => this.queries.delete(query));
|
|
6991
|
+
}
|
|
6992
|
+
await seatQuery.deliverResults(context);
|
|
6993
|
+
while (true) {
|
|
6994
|
+
const message = await seatQuery.next();
|
|
6995
|
+
if (message.type === "assistant") response.assistant(message);
|
|
6996
|
+
else if (message.type === "stream_event") {
|
|
6997
|
+
if (response.partial(message) && response.message.content.some((block) => block.type === "toolCall")) {
|
|
6998
|
+
seatQuery.park(response.message);
|
|
6999
|
+
keepQuery = true;
|
|
7000
|
+
response.done("toolUse");
|
|
7001
|
+
return;
|
|
7002
|
+
}
|
|
7003
|
+
} else if (message.type === "result") {
|
|
7004
|
+
if (message.is_error) throw new Error("errors" in message ? message.errors.join("; ") : message.result);
|
|
7005
|
+
const usage = mapUsage(message.usage);
|
|
7006
|
+
response.message.usage = remainingSeatUsage({
|
|
7007
|
+
total: usage,
|
|
7008
|
+
reported: seatQuery.reportedUsage,
|
|
7009
|
+
log: log$3,
|
|
7010
|
+
sessionId: this.deps.sessionId,
|
|
7011
|
+
model: model.id
|
|
7012
|
+
});
|
|
7013
|
+
response.done("stop");
|
|
7014
|
+
return;
|
|
7015
|
+
} else if (message.type === "rate_limit_event") {
|
|
7016
|
+
const info = message.rate_limit_info;
|
|
7017
|
+
await options.onResponse?.({
|
|
7018
|
+
status: info.status === "rejected" ? 429 : 200,
|
|
7019
|
+
headers: {
|
|
7020
|
+
"anthropic-ratelimit-unified-status": info.status,
|
|
7021
|
+
...info.resetsAt === void 0 ? {} : { "anthropic-ratelimit-unified-reset": String(info.resetsAt) },
|
|
7022
|
+
...info.utilization === void 0 ? {} : { "anthropic-ratelimit-unified-overall-percent": String(Math.round(info.utilization * 100)) }
|
|
7023
|
+
}
|
|
7024
|
+
}, model);
|
|
7025
|
+
} else if (message.type === "system" && message.subtype === "api_retry" && message.error_status === 401) throw new Error("Claude seat authentication failed (401)");
|
|
7026
|
+
}
|
|
7027
|
+
} catch (err) {
|
|
7028
|
+
const reason = (seatQuery?.controller.signal.aborted || signal.aborted) && !(err instanceof Error && err.name === "TimeoutError") ? "aborted" : "error";
|
|
7029
|
+
if (reason === "error") log$3.error({
|
|
7030
|
+
err,
|
|
7031
|
+
sessionId: this.deps.sessionId
|
|
7032
|
+
}, "Claude seat SDK response failed");
|
|
7033
|
+
events.push({
|
|
7034
|
+
type: "error",
|
|
7035
|
+
reason,
|
|
7036
|
+
error: {
|
|
7037
|
+
...response.message,
|
|
7038
|
+
stopReason: reason,
|
|
7039
|
+
errorMessage: err instanceof Error ? err.message : String(err)
|
|
7040
|
+
}
|
|
7041
|
+
});
|
|
7042
|
+
} finally {
|
|
7043
|
+
if (!keepQuery) seatQuery?.abort();
|
|
7044
|
+
}
|
|
7045
|
+
})();
|
|
7046
|
+
return events;
|
|
7047
|
+
}
|
|
7048
|
+
};
|
|
7049
|
+
function createClaudeSeatSdkExtension(deps) {
|
|
7050
|
+
return (pi) => {
|
|
7051
|
+
let bridge = null;
|
|
7052
|
+
pi.registerProvider("anthropic", {
|
|
7053
|
+
api: SEAT_SDK_API,
|
|
7054
|
+
streamSimple: (model, context, options) => {
|
|
7055
|
+
if (!bridge) throw new Error("Claude seat SDK session has not started");
|
|
7056
|
+
return bridge.stream(model, context, options);
|
|
7057
|
+
}
|
|
7058
|
+
});
|
|
7059
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
7060
|
+
bridge?.abort();
|
|
7061
|
+
bridge = new SeatSdkBridge({
|
|
7062
|
+
...deps,
|
|
7063
|
+
cwd: ctx.cwd
|
|
7064
|
+
});
|
|
7065
|
+
});
|
|
7066
|
+
pi.on("agent_end", async () => {
|
|
7067
|
+
bridge?.abort();
|
|
7068
|
+
});
|
|
7069
|
+
pi.on("session_before_switch", async () => {
|
|
7070
|
+
bridge?.abort();
|
|
7071
|
+
});
|
|
7072
|
+
pi.on("session_shutdown", async () => {
|
|
7073
|
+
bridge?.abort();
|
|
7074
|
+
});
|
|
7075
|
+
};
|
|
7076
|
+
}
|
|
7077
|
+
//#endregion
|
|
5824
7078
|
//#region src/extensions/self-trace.ts
|
|
5825
7079
|
const log$2 = logger.child({ module: "self-trace-extension" });
|
|
5826
7080
|
/**
|
|
@@ -6333,6 +7587,7 @@ const all = [
|
|
|
6333
7587
|
function platformExtensions({ sessionId, channelContext }) {
|
|
6334
7588
|
return [
|
|
6335
7589
|
providerApiCompatExtension,
|
|
7590
|
+
createClaudeSeatSdkExtension({ sessionId }),
|
|
6336
7591
|
createPlatformExtensions({
|
|
6337
7592
|
sessionId,
|
|
6338
7593
|
channelContext
|