@zhin.js/agent 1.1.39 → 1.1.40
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/lib/assistant/job-storage.d.ts +238 -0
- package/lib/assistant/job-storage.js +143 -0
- package/lib/assistant/job-storage.js.map +1 -0
- package/lib/assistant/job-store.d.ts +7 -0
- package/lib/assistant/job-store.js +41 -3
- package/lib/assistant/job-store.js.map +1 -1
- package/lib/config/perception-config.d.ts +9 -0
- package/lib/config/perception-config.js +27 -0
- package/lib/config/perception-config.js.map +1 -1
- package/lib/context/context-system.d.ts +1 -0
- package/lib/context/context-system.js +8 -8
- package/lib/context/context-system.js.map +1 -1
- package/lib/context/im-turn-context-adapter.js +2 -0
- package/lib/context/im-turn-context-adapter.js.map +1 -1
- package/lib/context/skill-context.d.ts +3 -0
- package/lib/context/skill-context.js +6 -0
- package/lib/context/skill-context.js.map +1 -0
- package/lib/context/turn-envelope.d.ts +4 -0
- package/lib/context/turn-envelope.js +24 -7
- package/lib/context/turn-envelope.js.map +1 -1
- package/lib/context/turn-user-message.d.ts +3 -1
- package/lib/context/turn-user-message.js +14 -1
- package/lib/context/turn-user-message.js.map +1 -1
- package/lib/core/agent-core-run.d.ts +1 -0
- package/lib/core/agent-core-run.js +47 -20
- package/lib/core/agent-core-run.js.map +1 -1
- package/lib/core/structured-reply-recovery.d.ts +6 -2
- package/lib/core/structured-reply-recovery.js +22 -8
- package/lib/core/structured-reply-recovery.js.map +1 -1
- package/lib/core/turn-completion-usage.d.ts +2 -1
- package/lib/core/turn-completion-usage.js +14 -5
- package/lib/core/turn-completion-usage.js.map +1 -1
- package/lib/core/turn-event-mapper.d.ts +1 -1
- package/lib/core/turn-event-mapper.js +8 -5
- package/lib/core/turn-event-mapper.js.map +1 -1
- package/lib/event/turn-event.d.ts +7 -1
- package/lib/event/turn-event.js.map +1 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/media/perception.d.ts +3 -4
- package/lib/media/perception.js +59 -37
- package/lib/media/perception.js.map +1 -1
- package/lib/memory/compaction-runtime.d.ts +3 -2
- package/lib/memory/compaction-runtime.js +22 -14
- package/lib/memory/compaction-runtime.js.map +1 -1
- package/lib/outbound/agent-output.js +2 -0
- package/lib/outbound/agent-output.js.map +1 -1
- package/lib/outbound/chat-failure.d.ts +27 -0
- package/lib/outbound/chat-failure.js +53 -0
- package/lib/outbound/chat-failure.js.map +1 -0
- package/lib/outbound/structured-reply.d.ts +7 -0
- package/lib/outbound/structured-reply.js +52 -16
- package/lib/outbound/structured-reply.js.map +1 -1
- package/lib/plugin-runtime/agent-runtime.js +49 -8
- package/lib/plugin-runtime/agent-runtime.js.map +1 -1
- package/lib/plugin-runtime/ai-completion.d.ts +4 -3
- package/lib/plugin-runtime/ai-completion.js +64 -10
- package/lib/plugin-runtime/ai-completion.js.map +1 -1
- package/lib/plugin-runtime/capability-ingress.js +3 -1
- package/lib/plugin-runtime/capability-ingress.js.map +1 -1
- package/lib/plugin-runtime/deferred-capability-plan.d.ts +4 -1
- package/lib/plugin-runtime/deferred-capability-plan.js +25 -9
- package/lib/plugin-runtime/deferred-capability-plan.js.map +1 -1
- package/lib/plugin-runtime/full-agent-turn-engine.js +1 -0
- package/lib/plugin-runtime/full-agent-turn-engine.js.map +1 -1
- package/lib/plugin-runtime/index.d.ts +2 -0
- package/lib/plugin-runtime/index.js +2 -0
- package/lib/plugin-runtime/index.js.map +1 -1
- package/lib/prompt/assembly.js +0 -9
- package/lib/prompt/assembly.js.map +1 -1
- package/lib/prompt/git-context.js +1 -1
- package/lib/prompt/git-context.js.map +1 -1
- package/lib/prompt/rules/chat-rules.js +8 -9
- package/lib/prompt/rules/chat-rules.js.map +1 -1
- package/lib/prompt/rules/critical-rules.js +10 -10
- package/lib/prompt/rules/critical-rules.js.map +1 -1
- package/lib/prompt/system-prompt.js +4 -5
- package/lib/prompt/system-prompt.js.map +1 -1
- package/lib/security/file-role-policy.js +6 -6
- package/lib/security/file-role-policy.js.map +1 -1
- package/lib/security/policy-facade.js +1 -1
- package/lib/security/policy-facade.js.map +1 -1
- package/lib/security/usage-quota.d.ts +4 -0
- package/lib/security/usage-quota.js +23 -2
- package/lib/security/usage-quota.js.map +1 -1
- package/lib/session/approval-port.d.ts +2 -0
- package/lib/session/approval-port.js.map +1 -1
- package/lib/session/approval-review-agent.d.ts +1 -1
- package/lib/session/approval-review-agent.js +19 -4
- package/lib/session/approval-review-agent.js.map +1 -1
- package/lib/session/turn-ingress-session.js +7 -1
- package/lib/session/turn-ingress-session.js.map +1 -1
- package/lib/tool/tool-approval-gate.js +4 -2
- package/lib/tool/tool-approval-gate.js.map +1 -1
- package/lib/tool/turn-tool-runtime.js +24 -5
- package/lib/tool/turn-tool-runtime.js.map +1 -1
- package/lib/tool-catalog/deferred-turn-controller.js +1 -4
- package/lib/tool-catalog/deferred-turn-controller.js.map +1 -1
- package/lib/trace/context-budget.d.ts +4 -3
- package/lib/trace/context-budget.js +28 -8
- package/lib/trace/context-budget.js.map +1 -1
- package/lib/trace/turn-trace-format.js +4 -1
- package/lib/trace/turn-trace-format.js.map +1 -1
- package/lib/trace/turn-trace.d.ts +6 -0
- package/lib/trace/turn-trace.js +8 -2
- package/lib/trace/turn-trace.js.map +1 -1
- package/lib/trace/turn-usage-log.d.ts +14 -0
- package/lib/trace/turn-usage-log.js +83 -0
- package/lib/trace/turn-usage-log.js.map +1 -0
- package/lib/usage/usage-store.d.ts +199 -0
- package/lib/usage/usage-store.js +121 -0
- package/lib/usage/usage-store.js.map +1 -0
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-approval-gate.js","sourceRoot":"","sources":["../../src/tool/tool-approval-gate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,oBAAoB,EAA2B,MAAM,0BAA0B,CAAC;AAG1G,OAAO,EAAE,uBAAuB,EAAqB,MAAM,6BAA6B,CAAC;AAKzF,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,MAA0C,EAC1C,KAA6E,EAC7E,SAAiC;IAEjC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAChD,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-approval-gate.js","sourceRoot":"","sources":["../../src/tool/tool-approval-gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-tool approval gate — stacks with ExecPolicy (ADR 0039 P1).\n */\nimport type { Message } from '@zhin.js/core';\nimport { AgentRunJournal, AgentStreamEventType, type AgentRunEventInput } from '@zhin.js/ai/agent-stream';\nimport type { ToolApprovalPolicy } from '@zhin.js/ai/tool-policy';\nimport type { AgentStreamBus, AgentStreamPublishContext } from '../event/agent-stream-bus.js';\nimport { isApprovalPortAvailable, type ApprovalPort } from '../session/approval-port.js';\nimport type { ToolApprovalOnceStore } from './tool-approval-once-store.js';\n\nexport type ToolApprovalGatePolicy = ToolApprovalPolicy;\n\nexport async function resolveToolApprovalRequired(\n policy: ToolApprovalGatePolicy | undefined,\n input: { toolName: string; args: Record<string, unknown>; sessionId: string },\n onceStore?: ToolApprovalOnceStore,\n): Promise<boolean> {\n if (!policy || policy === 'never') return false;\n if (policy === 'always' || policy === 'on-risk' || policy === 'human') return true;\n if (policy === 'once') {\n if (!onceStore) return true;\n return !onceStore.has(input.sessionId, input.toolName);\n }\n return Boolean(await policy({ toolName: input.toolName, args: input.args }));\n}\n\nexport interface ToolApprovalGateOptions {\n toolName: string;\n args: Record<string, unknown>;\n sessionId: string;\n commMessage: Message;\n policy?: ToolApprovalGatePolicy;\n bus?: AgentStreamBus;\n port?: ApprovalPort;\n onceStore?: ToolApprovalOnceStore;\n publishCtx?: AgentStreamPublishContext;\n /** Stream-turn journal; request/result become ordered run events when present. */\n journal?: AgentRunJournal;\n signal: AbortSignal;\n}\n\n/**\n * Returns denial message when approval is required but not granted; otherwise null.\n */\nexport async function runToolApprovalGate(\n options: ToolApprovalGateOptions,\n): Promise<string | null> {\n const required = await resolveToolApprovalRequired(options.policy, {\n toolName: options.toolName,\n args: options.args,\n sessionId: options.sessionId,\n }, options.onceStore);\n if (!required) return null;\n\n if (!isApprovalPortAvailable(options.port)) {\n return 'Error: approval required but ApprovalPort unavailable';\n }\n\n const requestId = `approval_${options.toolName}_${Date.now()}`;\n const publishCtx: AgentStreamPublishContext = {\n sessionId: options.sessionId,\n ...options.publishCtx,\n };\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_REQUESTED,\n data: {\n sessionId: options.sessionId,\n requestId,\n sessionKey: options.sessionId,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n kind: 'approval',\n args: options.args,\n },\n }, publishCtx);\n\n const question = `工具「${options.toolName}」需要确认后执行。是否继续?`\n + (options.policy === 'human' ? `\\n本次参数:${stableApprovalValue(options.args)}` : '');\n let approved = false;\n let failure: string | undefined;\n try {\n approved = await options.port.requestApproval({\n ...(options.policy === 'human' ? { humanOnly: true } : {}),\n requestId,\n sessionKey: options.sessionId,\n conversationScope: options.commMessage.conversation.kind,\n requesterId: options.commMessage.sender?.id,\n toolName: options.toolName,\n scopeKey: `${options.toolName}:${stableApprovalValue(options.args)}`,\n question,\n signal: options.signal,\n });\n } catch (err) {\n failure = err instanceof Error ? err.message : String(err);\n }\n\n await publishApprovalEvent(options, {\n type: AgentStreamEventType.INPUT_COMPLETED,\n data: {\n sessionId: options.sessionId,\n requestId,\n toolName: options.toolName,\n kind: 'approval',\n approved,\n ...(failure ? { error: failure } : {}),\n },\n }, publishCtx);\n\n if (failure) return `Error: approval wait failed — ${failure}`;\n\n if (!approved) {\n return `Error: tool \"${options.toolName}\" execution denied by user`;\n }\n\n if (options.policy === 'once' && options.onceStore) {\n options.onceStore.add(options.sessionId, options.toolName);\n }\n return null;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\nasync function publishApprovalEvent(\n options: ToolApprovalGateOptions,\n event: AgentRunEventInput,\n ctx: AgentStreamPublishContext,\n): Promise<void> {\n const journalled = options.journal?.append(event);\n if (options.journal && !journalled) return;\n if (options.bus) await options.bus.publish(journalled ?? event, ctx);\n}\n"]}
|
|
@@ -39,6 +39,22 @@ export class TurnToolRuntime {
|
|
|
39
39
|
}
|
|
40
40
|
let effectiveInput = decision.input;
|
|
41
41
|
const highRisk = classifyHighRiskAction(tool);
|
|
42
|
+
const humanOnly = tool.requiresApproval === 'human';
|
|
43
|
+
if (humanOnly) {
|
|
44
|
+
try {
|
|
45
|
+
// Caller-owned nested values must not change the operation while a person reviews it.
|
|
46
|
+
effectiveInput = structuredClone(effectiveInput);
|
|
47
|
+
// Reject cycles and other non-serializable input before constructing the approval card.
|
|
48
|
+
JSON.stringify(effectiveInput);
|
|
49
|
+
if (highRisk === 'outbound' && this.#leakInspector) {
|
|
50
|
+
// Show exactly what will execute; do not redact approved arguments afterward.
|
|
51
|
+
effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return this.#deny(name, toolUseId, 'approval', 'human approval requires an isolated JSON argument snapshot', causedBy);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
42
58
|
const provenanceGate = highRisk !== undefined
|
|
43
59
|
&& this.#guard.confirmHighRisk === 'external_content'
|
|
44
60
|
&& this.provenance.tainted;
|
|
@@ -50,6 +66,7 @@ export class TurnToolRuntime {
|
|
|
50
66
|
}
|
|
51
67
|
const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);
|
|
52
68
|
const approval = await requestApprovalOutcome(this.turn.ports.approval, {
|
|
69
|
+
...(humanOnly ? { humanOnly: true } : {}),
|
|
53
70
|
requestId: `${this.turn.identity.turnId}:${toolUseId}`,
|
|
54
71
|
sessionKey: this.turn.session.key,
|
|
55
72
|
...(this.turn.origin.kind === 'im'
|
|
@@ -59,7 +76,7 @@ export class TurnToolRuntime {
|
|
|
59
76
|
toolName: name,
|
|
60
77
|
// Never matches a remembered approval: each tainted call is confirmed on its own.
|
|
61
78
|
scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,
|
|
62
|
-
question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput),
|
|
79
|
+
question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput, humanOnly),
|
|
63
80
|
signal: this.turn.signal,
|
|
64
81
|
});
|
|
65
82
|
if (this.turn.signal.aborted)
|
|
@@ -73,6 +90,7 @@ export class TurnToolRuntime {
|
|
|
73
90
|
return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);
|
|
74
91
|
}
|
|
75
92
|
const approval = await requestApprovalOutcome(this.turn.ports.approval, {
|
|
93
|
+
...(humanOnly ? { humanOnly: true } : {}),
|
|
76
94
|
requestId: `${this.turn.identity.turnId}:${toolUseId}`,
|
|
77
95
|
sessionKey: this.turn.session.key,
|
|
78
96
|
...(this.turn.origin.kind === 'im'
|
|
@@ -82,11 +100,11 @@ export class TurnToolRuntime {
|
|
|
82
100
|
toolName: name,
|
|
83
101
|
scopeKey: tool.requiresApproval === 'once'
|
|
84
102
|
? name
|
|
85
|
-
: approvalScopeKey(name,
|
|
103
|
+
: approvalScopeKey(name, effectiveInput),
|
|
86
104
|
...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'
|
|
87
105
|
? { remember: 'session' }
|
|
88
106
|
: {}),
|
|
89
|
-
question: formatApprovalQuestion(name, decision.reason,
|
|
107
|
+
question: formatApprovalQuestion(name, decision.reason, effectiveInput, humanOnly),
|
|
90
108
|
signal: this.turn.signal,
|
|
91
109
|
});
|
|
92
110
|
if (this.turn.signal.aborted)
|
|
@@ -95,7 +113,7 @@ export class TurnToolRuntime {
|
|
|
95
113
|
return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);
|
|
96
114
|
}
|
|
97
115
|
}
|
|
98
|
-
if (highRisk === 'outbound' && this.#leakInspector) {
|
|
116
|
+
if (!humanOnly && highRisk === 'outbound' && this.#leakInspector) {
|
|
99
117
|
// #727 M3.4: messages to other conversations go through the same leak check as replies.
|
|
100
118
|
effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;
|
|
101
119
|
}
|
|
@@ -160,7 +178,7 @@ export class TurnToolRuntime {
|
|
|
160
178
|
}
|
|
161
179
|
}
|
|
162
180
|
}
|
|
163
|
-
function formatApprovalQuestion(toolName, reason, input) {
|
|
181
|
+
function formatApprovalQuestion(toolName, reason, input, humanOnly) {
|
|
164
182
|
const command = typeof input.command === 'string' ? input.command.trim() : '';
|
|
165
183
|
const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';
|
|
166
184
|
const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string');
|
|
@@ -169,6 +187,7 @@ function formatApprovalQuestion(toolName, reason, input) {
|
|
|
169
187
|
command ? `命令:${command}` : undefined,
|
|
170
188
|
cwd ? `工作目录:${cwd}` : undefined,
|
|
171
189
|
path ? `目标文件:${path}` : undefined,
|
|
190
|
+
humanOnly ? `本次参数:${stableApprovalValue(input)}` : undefined,
|
|
172
191
|
`风险:${reason}`,
|
|
173
192
|
'是否继续?',
|
|
174
193
|
].filter((line) => Boolean(line)).join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-tool-runtime.js","sourceRoot":"","sources":["../../src/tool/turn-tool-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { runTurnToolPolicies } from '../security/policy-facade.js';\nimport type { ToolCapability } from '../plugin-runtime/capability-ingress.js';\nimport { toolInvocationFromTurn } from '../plugin-runtime/capability-tools.js';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport {\n formatApprovalDenial,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n} from '../session/approval-port.js';\nimport type { AgentTool, ToolExecutionCause } from '@zhin.js/ai';\nimport type { ToolExecutionAuthority } from '../core/tool-execution-authority.js';\nimport { TurnJournalCommitError } from '../turn/journal-integrity.js';\nimport { NetworkAccessDeniedError } from '../security/network-policy.js';\nimport {\n PROVENANCE_POLICY,\n PROVENANCE_UNATTENDED_REASON,\n TurnProvenance,\n classifyHighRiskAction,\n formatProvenanceReason,\n isExternalContentSource,\n resolveInjectionGuard,\n type InjectionGuardSettings,\n} from '../security/provenance.js';\nimport type { OutboundLeakInspector } from '../security/outbound-leak-guard.js';\nimport { resolveTurnTrustLevel } from '../security/trust-level.js';\nimport { safeSessionKey } from '../memory-layers.js';\n\ntype ToolJournalEvent = Extract<import('../event/turn-event.js').TurnEvent, {\n type: 'tool_call' | 'tool_result' | 'tool_denied' | 'tool_failed' | 'tool_cancelled';\n}>;\n\nexport type TurnToolOutcome =\n | Readonly<{ status: 'completed'; output: unknown; durationMs: number }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'cancelled'; reason: string }>\n | Readonly<{ status: 'failed'; error: string; retryable: boolean }>;\n\nexport interface TurnToolRuntimeOptions {\n /** `ai.agent.injectionGuard`; unset keys use the conservative defaults. */\n readonly injectionGuard?: Partial<InjectionGuardSettings>;\n /** Shared with the engine so sources outside tool calls (background events) also count. */\n readonly provenance?: TurnProvenance;\n /** Masks leaks in the text arguments of outbound tools and learns other-session memory reads. */\n readonly leakInspector?: OutboundLeakInspector;\n}\n\n/** Sole execution authority for Tool capabilities within one fixed Turn lease. */\nexport class TurnToolRuntime {\n readonly #tools: ReadonlyMap<string, ToolCapability>;\n readonly #guard: InjectionGuardSettings;\n readonly #leakInspector?: OutboundLeakInspector;\n /** Lower-trust sources that reached the model during this turn (#727). */\n readonly provenance: TurnProvenance;\n\n constructor(\n private readonly turn: TurnIngress,\n tools: readonly ToolCapability[],\n options: TurnToolRuntimeOptions = {},\n ) {\n this.#tools = new Map(tools.map((tool) => [tool.name, tool]));\n this.#guard = resolveInjectionGuard(options.injectionGuard);\n this.provenance = options.provenance ?? new TurnProvenance();\n this.#leakInspector = this.#guard.outboundCheck === 'redact' ? options.leakInspector : undefined;\n }\n\n async execute(\n name: string,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ): Promise<TurnToolOutcome> {\n const tool = this.#tools.get(name);\n const causedBy = freezeToolExecutionCause(cause);\n if (!tool) return this.#deny(name, toolUseId, 'capability', `Unknown Tool capability: ${name}`, causedBy);\n await this.#append({ type: 'tool_call', toolName: name, args: { ...input }, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n const decision = await runTurnToolPolicies({ turn: this.turn, tool, input });\n // File/cwd authorization is asynchronous: cancellation can arrive after\n // the ingress check but before an allowed decision reaches execution.\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (decision.status === 'denied') {\n return this.#deny(name, toolUseId, decision.policy, decision.reason, causedBy);\n }\n let effectiveInput = decision.input;\n const highRisk = classifyHighRiskAction(tool);\n const provenanceGate = highRisk !== undefined\n && this.#guard.confirmHighRisk === 'external_content'\n && this.provenance.tainted;\n if (provenanceGate) {\n // #727 M3.3: a high-risk action after lower-trust content always asks\n // first, even when the configured policy would allow it outright.\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, PROVENANCE_UNATTENDED_REASON, causedBy);\n }\n const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n // Never matches a remembered approval: each tainted call is confirmed on its own.\n scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,\n question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, formatApprovalDenial(approval), causedBy);\n }\n } else if (decision.status === 'approval_required') {\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);\n }\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n scopeKey: tool.requiresApproval === 'once'\n ? name\n : approvalScopeKey(name, decision.input),\n ...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'\n ? { remember: 'session' as const }\n : {}),\n question: formatApprovalQuestion(name, decision.reason, decision.input),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);\n }\n }\n\n if (highRisk === 'outbound' && this.#leakInspector) {\n // #727 M3.4: messages to other conversations go through the same leak check as replies.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n\n const startedAt = Date.now();\n try {\n const output = await tool.execute(effectiveInput, toolInvocationFromTurn(this.turn));\n this.#observeOutput(tool, effectiveInput, output);\n const durationMs = Date.now() - startedAt;\n // A tool that ignores cancellation may already have committed side effects.\n // Record its real settlement so the audit trail never invites an unsafe retry.\n await this.#append({ type: 'tool_result', toolName: name, output, durationMs, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancelledOutcome();\n return Object.freeze({ status: 'completed', output, durationMs });\n } catch (error) {\n if (error instanceof TurnJournalCommitError) throw error;\n const durationMs = Date.now() - startedAt;\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, durationMs, causedBy);\n if (error instanceof NetworkAccessDeniedError) {\n return this.#deny(name, toolUseId, error.policy, error.message, causedBy);\n }\n const message = error instanceof Error ? error.message : String(error);\n await this.#append({ type: 'tool_failed', toolName: name, toolUseId, error: message, durationMs }, causedBy);\n return Object.freeze({ status: 'failed', error: message, retryable: false });\n }\n }\n\n /** Records where the result came from, and which parts are another session's memory. */\n #observeOutput(tool: ToolCapability, input: Readonly<Record<string, unknown>>, output: unknown): void {\n if (isExternalContentSource(tool, this.#guard)) {\n this.provenance.record(tool.name === 'spawn_task'\n ? { kind: 'subagent_report' }\n : { kind: 'tool_result', toolName: tool.name });\n }\n if (this.#leakInspector && readsOtherSessionMemory(input, this.turn.session.key)) {\n this.#leakInspector.protectOtherSessionMemory(collectText(output));\n }\n }\n\n async #deny(toolName: string, toolUseId: string, policy: string, reason: string, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n await this.#append({ type: 'tool_denied', toolName, toolUseId, policy, reason }, causedBy);\n return Object.freeze({ status: 'denied', policy, reason });\n }\n\n async #cancel(toolName: string, toolUseId: string, durationMs: number, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n const outcome = this.#cancelledOutcome();\n await this.#append({ type: 'tool_cancelled', toolName, toolUseId, reason: outcome.reason, durationMs }, causedBy);\n return outcome;\n }\n\n #cancelledOutcome(): Extract<TurnToolOutcome, { status: 'cancelled' }> {\n const reason = this.turn.signal.reason instanceof Error\n ? this.turn.signal.reason.message\n : String(this.turn.signal.reason ?? 'turn cancelled');\n return Object.freeze({ status: 'cancelled', reason });\n }\n\n async #append(event: ToolJournalEvent, causedBy?: ToolExecutionCause): Promise<void> {\n try {\n await this.turn.ports.journal.append(causedBy ? { ...event, causedBy } : event);\n } catch (error) {\n throw new TurnJournalCommitError(error);\n }\n }\n}\n\nfunction formatApprovalQuestion(\n toolName: string,\n reason: string,\n input: Readonly<Record<string, unknown>>,\n): string {\n const command = typeof input.command === 'string' ? input.command.trim() : '';\n const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';\n const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n return [\n `工具「${toolName}」需要确认后执行。`,\n command ? `命令:${command}` : undefined,\n cwd ? `工作目录:${cwd}` : undefined,\n path ? `目标文件:${path}` : undefined,\n `风险:${reason}`,\n '是否继续?',\n ].filter((line): line is string => Boolean(line)).join('\\n');\n}\n\nconst SESSION_MEMORY_PATH = /(?:^|[\\\\/])data[\\\\/]memory[\\\\/]sessions[\\\\/]([^\\\\/]+)/;\n\n/** True when a file tool's path points into a different session's memory directory. */\nfunction readsOtherSessionMemory(input: Readonly<Record<string, unknown>>, sessionKey: string): boolean {\n const path = ['file_path', 'path', 'filePath'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n const match = path ? SESSION_MEMORY_PATH.exec(path) : null;\n return match !== null && match[1] !== safeSessionKey(sessionKey);\n}\n\n/** Every string inside a tool output, one per line (file tools may return structured results). */\nfunction collectText(value: unknown, depth = 0): string {\n if (typeof value === 'string') return value;\n if (depth > 4 || value === null || typeof value !== 'object') return '';\n const entries = Array.isArray(value) ? value : Object.values(value as Record<string, unknown>);\n return entries.map((entry) => collectText(entry, depth + 1)).filter(Boolean).join('\\n');\n}\n\nfunction approvalScopeKey(toolName: string, input: Readonly<Record<string, unknown>>): string {\n return `${toolName}:${stableApprovalValue(input)}`;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\n/** Adapts canonical capability execution to the full AgentCore seam. */\nexport class TurnToolExecutionAuthority implements ToolExecutionAuthority {\n readonly #runtime: TurnToolRuntime;\n\n constructor(runtime: TurnToolRuntime) {\n this.#runtime = runtime;\n }\n\n execute(\n tool: AgentTool,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ) {\n return this.#runtime.execute(tool.name, input, toolUseId, cause);\n }\n}\n\nfunction freezeToolExecutionCause(cause?: ToolExecutionCause): ToolExecutionCause | undefined {\n if (!cause?.principal && !cause?.turn) return undefined;\n return Object.freeze({\n ...(cause.principal ? {\n principal: Object.freeze({\n ...cause.principal,\n ...(cause.principal.roles ? { roles: Object.freeze([...cause.principal.roles]) } : {}),\n }),\n } : {}),\n ...(cause.turn ? { turn: Object.freeze({ ...cause.turn }) } : {}),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"turn-tool-runtime.js","sourceRoot":"","sources":["../../src/tool/turn-tool-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { runTurnToolPolicies } from '../security/policy-facade.js';\nimport type { ToolCapability } from '../plugin-runtime/capability-ingress.js';\nimport { toolInvocationFromTurn } from '../plugin-runtime/capability-tools.js';\nimport type { TurnIngress } from '../turn/turn-ingress.js';\nimport {\n formatApprovalDenial,\n isApprovalPortAvailable,\n requestApprovalOutcome,\n} from '../session/approval-port.js';\nimport type { AgentTool, ToolExecutionCause } from '@zhin.js/ai';\nimport type { ToolExecutionAuthority } from '../core/tool-execution-authority.js';\nimport { TurnJournalCommitError } from '../turn/journal-integrity.js';\nimport { NetworkAccessDeniedError } from '../security/network-policy.js';\nimport {\n PROVENANCE_POLICY,\n PROVENANCE_UNATTENDED_REASON,\n TurnProvenance,\n classifyHighRiskAction,\n formatProvenanceReason,\n isExternalContentSource,\n resolveInjectionGuard,\n type InjectionGuardSettings,\n} from '../security/provenance.js';\nimport type { OutboundLeakInspector } from '../security/outbound-leak-guard.js';\nimport { resolveTurnTrustLevel } from '../security/trust-level.js';\nimport { safeSessionKey } from '../memory-layers.js';\n\ntype ToolJournalEvent = Extract<import('../event/turn-event.js').TurnEvent, {\n type: 'tool_call' | 'tool_result' | 'tool_denied' | 'tool_failed' | 'tool_cancelled';\n}>;\n\nexport type TurnToolOutcome =\n | Readonly<{ status: 'completed'; output: unknown; durationMs: number }>\n | Readonly<{ status: 'denied'; policy: string; reason: string }>\n | Readonly<{ status: 'cancelled'; reason: string }>\n | Readonly<{ status: 'failed'; error: string; retryable: boolean }>;\n\nexport interface TurnToolRuntimeOptions {\n /** `ai.agent.injectionGuard`; unset keys use the conservative defaults. */\n readonly injectionGuard?: Partial<InjectionGuardSettings>;\n /** Shared with the engine so sources outside tool calls (background events) also count. */\n readonly provenance?: TurnProvenance;\n /** Masks leaks in the text arguments of outbound tools and learns other-session memory reads. */\n readonly leakInspector?: OutboundLeakInspector;\n}\n\n/** Sole execution authority for Tool capabilities within one fixed Turn lease. */\nexport class TurnToolRuntime {\n readonly #tools: ReadonlyMap<string, ToolCapability>;\n readonly #guard: InjectionGuardSettings;\n readonly #leakInspector?: OutboundLeakInspector;\n /** Lower-trust sources that reached the model during this turn (#727). */\n readonly provenance: TurnProvenance;\n\n constructor(\n private readonly turn: TurnIngress,\n tools: readonly ToolCapability[],\n options: TurnToolRuntimeOptions = {},\n ) {\n this.#tools = new Map(tools.map((tool) => [tool.name, tool]));\n this.#guard = resolveInjectionGuard(options.injectionGuard);\n this.provenance = options.provenance ?? new TurnProvenance();\n this.#leakInspector = this.#guard.outboundCheck === 'redact' ? options.leakInspector : undefined;\n }\n\n async execute(\n name: string,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ): Promise<TurnToolOutcome> {\n const tool = this.#tools.get(name);\n const causedBy = freezeToolExecutionCause(cause);\n if (!tool) return this.#deny(name, toolUseId, 'capability', `Unknown Tool capability: ${name}`, causedBy);\n await this.#append({ type: 'tool_call', toolName: name, args: { ...input }, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n const decision = await runTurnToolPolicies({ turn: this.turn, tool, input });\n // File/cwd authorization is asynchronous: cancellation can arrive after\n // the ingress check but before an allowed decision reaches execution.\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (decision.status === 'denied') {\n return this.#deny(name, toolUseId, decision.policy, decision.reason, causedBy);\n }\n let effectiveInput = decision.input;\n const highRisk = classifyHighRiskAction(tool);\n const humanOnly = tool.requiresApproval === 'human';\n if (humanOnly) {\n try {\n // Caller-owned nested values must not change the operation while a person reviews it.\n effectiveInput = structuredClone(effectiveInput);\n // Reject cycles and other non-serializable input before constructing the approval card.\n JSON.stringify(effectiveInput);\n if (highRisk === 'outbound' && this.#leakInspector) {\n // Show exactly what will execute; do not redact approved arguments afterward.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n } catch {\n return this.#deny(name, toolUseId, 'approval', 'human approval requires an isolated JSON argument snapshot', causedBy);\n }\n }\n const provenanceGate = highRisk !== undefined\n && this.#guard.confirmHighRisk === 'external_content'\n && this.provenance.tainted;\n if (provenanceGate) {\n // #727 M3.3: a high-risk action after lower-trust content always asks\n // first, even when the configured policy would allow it outright.\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, PROVENANCE_UNATTENDED_REASON, causedBy);\n }\n const chain = this.provenance.describeChain(resolveTurnTrustLevel(this.turn), name);\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n ...(humanOnly ? { humanOnly: true } : {}),\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n // Never matches a remembered approval: each tainted call is confirmed on its own.\n scopeKey: `${PROVENANCE_POLICY}:${this.turn.identity.turnId}:${toolUseId}`,\n question: formatApprovalQuestion(name, formatProvenanceReason(highRisk, chain), effectiveInput,\n humanOnly),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, PROVENANCE_POLICY, formatApprovalDenial(approval), causedBy);\n }\n } else if (decision.status === 'approval_required') {\n if (this.turn.policy.unattended || !isApprovalPortAvailable(this.turn.ports.approval)) {\n return this.#deny(name, toolUseId, decision.policy, 'approval required but ApprovalPort unavailable', causedBy);\n }\n const approval = await requestApprovalOutcome(this.turn.ports.approval, {\n ...(humanOnly ? { humanOnly: true } : {}),\n requestId: `${this.turn.identity.turnId}:${toolUseId}`,\n sessionKey: this.turn.session.key,\n ...(this.turn.origin.kind === 'im'\n ? { conversationScope: this.turn.origin.scope }\n : {}),\n requesterId: this.turn.principal.subjectId,\n toolName: name,\n scopeKey: tool.requiresApproval === 'once'\n ? name\n : approvalScopeKey(name, effectiveInput),\n ...(tool.requiresApproval === 'once' || decision.policy === 'exec-policy'\n ? { remember: 'session' as const }\n : {}),\n question: formatApprovalQuestion(name, decision.reason, effectiveInput, humanOnly),\n signal: this.turn.signal,\n });\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, 0, causedBy);\n if (approval.status !== 'approved') {\n return this.#deny(name, toolUseId, decision.policy, formatApprovalDenial(approval), causedBy);\n }\n }\n\n if (!humanOnly && highRisk === 'outbound' && this.#leakInspector) {\n // #727 M3.4: messages to other conversations go through the same leak check as replies.\n effectiveInput = this.#leakInspector.inspectArguments(effectiveInput).input;\n }\n\n const startedAt = Date.now();\n try {\n const output = await tool.execute(effectiveInput, toolInvocationFromTurn(this.turn));\n this.#observeOutput(tool, effectiveInput, output);\n const durationMs = Date.now() - startedAt;\n // A tool that ignores cancellation may already have committed side effects.\n // Record its real settlement so the audit trail never invites an unsafe retry.\n await this.#append({ type: 'tool_result', toolName: name, output, durationMs, toolUseId }, causedBy);\n if (this.turn.signal.aborted) return this.#cancelledOutcome();\n return Object.freeze({ status: 'completed', output, durationMs });\n } catch (error) {\n if (error instanceof TurnJournalCommitError) throw error;\n const durationMs = Date.now() - startedAt;\n if (this.turn.signal.aborted) return this.#cancel(name, toolUseId, durationMs, causedBy);\n if (error instanceof NetworkAccessDeniedError) {\n return this.#deny(name, toolUseId, error.policy, error.message, causedBy);\n }\n const message = error instanceof Error ? error.message : String(error);\n await this.#append({ type: 'tool_failed', toolName: name, toolUseId, error: message, durationMs }, causedBy);\n return Object.freeze({ status: 'failed', error: message, retryable: false });\n }\n }\n\n /** Records where the result came from, and which parts are another session's memory. */\n #observeOutput(tool: ToolCapability, input: Readonly<Record<string, unknown>>, output: unknown): void {\n if (isExternalContentSource(tool, this.#guard)) {\n this.provenance.record(tool.name === 'spawn_task'\n ? { kind: 'subagent_report' }\n : { kind: 'tool_result', toolName: tool.name });\n }\n if (this.#leakInspector && readsOtherSessionMemory(input, this.turn.session.key)) {\n this.#leakInspector.protectOtherSessionMemory(collectText(output));\n }\n }\n\n async #deny(toolName: string, toolUseId: string, policy: string, reason: string, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n await this.#append({ type: 'tool_denied', toolName, toolUseId, policy, reason }, causedBy);\n return Object.freeze({ status: 'denied', policy, reason });\n }\n\n async #cancel(toolName: string, toolUseId: string, durationMs: number, causedBy?: ToolExecutionCause): Promise<TurnToolOutcome> {\n const outcome = this.#cancelledOutcome();\n await this.#append({ type: 'tool_cancelled', toolName, toolUseId, reason: outcome.reason, durationMs }, causedBy);\n return outcome;\n }\n\n #cancelledOutcome(): Extract<TurnToolOutcome, { status: 'cancelled' }> {\n const reason = this.turn.signal.reason instanceof Error\n ? this.turn.signal.reason.message\n : String(this.turn.signal.reason ?? 'turn cancelled');\n return Object.freeze({ status: 'cancelled', reason });\n }\n\n async #append(event: ToolJournalEvent, causedBy?: ToolExecutionCause): Promise<void> {\n try {\n await this.turn.ports.journal.append(causedBy ? { ...event, causedBy } : event);\n } catch (error) {\n throw new TurnJournalCommitError(error);\n }\n }\n}\n\nfunction formatApprovalQuestion(\n toolName: string,\n reason: string,\n input: Readonly<Record<string, unknown>>,\n humanOnly: boolean,\n): string {\n const command = typeof input.command === 'string' ? input.command.trim() : '';\n const cwd = typeof input.cwd === 'string' ? input.cwd.trim() : '';\n const path = ['file_path', 'path'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n return [\n `工具「${toolName}」需要确认后执行。`,\n command ? `命令:${command}` : undefined,\n cwd ? `工作目录:${cwd}` : undefined,\n path ? `目标文件:${path}` : undefined,\n humanOnly ? `本次参数:${stableApprovalValue(input)}` : undefined,\n `风险:${reason}`,\n '是否继续?',\n ].filter((line): line is string => Boolean(line)).join('\\n');\n}\n\nconst SESSION_MEMORY_PATH = /(?:^|[\\\\/])data[\\\\/]memory[\\\\/]sessions[\\\\/]([^\\\\/]+)/;\n\n/** True when a file tool's path points into a different session's memory directory. */\nfunction readsOtherSessionMemory(input: Readonly<Record<string, unknown>>, sessionKey: string): boolean {\n const path = ['file_path', 'path', 'filePath'].map((key) => input[key]).find((value) => typeof value === 'string') as string | undefined;\n const match = path ? SESSION_MEMORY_PATH.exec(path) : null;\n return match !== null && match[1] !== safeSessionKey(sessionKey);\n}\n\n/** Every string inside a tool output, one per line (file tools may return structured results). */\nfunction collectText(value: unknown, depth = 0): string {\n if (typeof value === 'string') return value;\n if (depth > 4 || value === null || typeof value !== 'object') return '';\n const entries = Array.isArray(value) ? value : Object.values(value as Record<string, unknown>);\n return entries.map((entry) => collectText(entry, depth + 1)).filter(Boolean).join('\\n');\n}\n\nfunction approvalScopeKey(toolName: string, input: Readonly<Record<string, unknown>>): string {\n return `${toolName}:${stableApprovalValue(input)}`;\n}\n\nfunction stableApprovalValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableApprovalValue).join(',')}]`;\n if (value && typeof value === 'object') {\n return `{${Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => `${JSON.stringify(key)}:${stableApprovalValue(entry)}`)\n .join(',')}}`;\n }\n if (typeof value === 'undefined') return 'undefined';\n try { return JSON.stringify(value); } catch { return String(value); }\n}\n\n/** Adapts canonical capability execution to the full AgentCore seam. */\nexport class TurnToolExecutionAuthority implements ToolExecutionAuthority {\n readonly #runtime: TurnToolRuntime;\n\n constructor(runtime: TurnToolRuntime) {\n this.#runtime = runtime;\n }\n\n execute(\n tool: AgentTool,\n input: Readonly<Record<string, unknown>>,\n toolUseId: string,\n cause?: ToolExecutionCause,\n ) {\n return this.#runtime.execute(tool.name, input, toolUseId, cause);\n }\n}\n\nfunction freezeToolExecutionCause(cause?: ToolExecutionCause): ToolExecutionCause | undefined {\n if (!cause?.principal && !cause?.turn) return undefined;\n return Object.freeze({\n ...(cause.principal ? {\n principal: Object.freeze({\n ...cause.principal,\n ...(cause.principal.roles ? { roles: Object.freeze([...cause.principal.roles]) } : {}),\n }),\n } : {}),\n ...(cause.turn ? { turn: Object.freeze({ ...cause.turn }) } : {}),\n });\n}\n"]}
|
|
@@ -22,10 +22,7 @@ function formatDiscoverResults(items, query) {
|
|
|
22
22
|
return items.map(item => `- [${item.kind}] ${item.name}: ${item.brief}`).join('\n');
|
|
23
23
|
}
|
|
24
24
|
function schemaSummary(item) {
|
|
25
|
-
|
|
26
|
-
if (!params)
|
|
27
|
-
return `Loaded tool "${item.name}".`;
|
|
28
|
-
return `Loaded tool "${item.name}".\nParameters schema:\n${JSON.stringify(params, null, 2)}`;
|
|
25
|
+
return `Loaded tool "${item.name}". Use its tool definition for arguments.`;
|
|
29
26
|
}
|
|
30
27
|
export function createDeferredTurnController(options) {
|
|
31
28
|
let currentSnapshot = structuredClone(options.snapshot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deferred-turn-controller.js","sourceRoot":"","sources":["../../src/tool-catalog/deferred-turn-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addSkillToSnapshot,\n getLoadedToolNamesFromSnapshot,\n touchToolInSnapshot,\n touchToolsInSnapshot,\n type AgentTool,\n type DeferredToolSessionSnapshot,\n} from '@zhin.js/ai';\nimport type { Tool, ToolResult } from '@zhin.js/core';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { SkillRegistry } from '../resource-hub/skill-registry.js';\nimport type { SkillInstructionSource } from '../skill/skill-instruction-reader.js';\nimport { catalogToolByName, discoverInCatalog, type DiscoverKind } from './tool-catalog.js';\nimport type { ToolCatalogItem } from './types.js';\n\nexport const TOOLS_MUTATED_MARKER = '__zhin_tools_mutated__';\n\nexport interface DeferredTurnControllerOptions {\n readonly sessionId: string;\n readonly platform?: string;\n readonly catalog: readonly ToolCatalogItem[];\n readonly skillRegistry: SkillRegistry | null;\n readonly snapshot: DeferredToolSessionSnapshot;\n readonly maxLoadedPerSession: number;\n /** Always-loaded tools: never recorded in the session snapshot, so they never count against or get evicted by `maxLoadedPerSession`. */\n readonly pinnedTools?: ReadonlySet<string>;\n readonly discoverTopK: number;\n readonly persistSnapshot: (snapshot: DeferredToolSessionSnapshot) => Promise<void>;\n readonly onSkillLoaded?: (name: string, instructions: string, toolNames: string[]) => void;\n readonly skillInstructions: SkillInstructionSource;\n}\n\nexport interface DeferredTurnController {\n readonly tools: readonly Tool[];\n snapshot(): DeferredToolSessionSnapshot;\n loadedToolNames(): string[];\n tool(name: string): AgentTool | undefined;\n /** Load a catalog tool into the session. Returns false when the name is not in this turn's catalog. */\n load(name: string): Promise<boolean>;\n fork(): DeferredTurnController;\n}\n\nconst activeController = new AsyncLocalStorage<DeferredTurnController>();\n\nexport function runWithDeferredTurnController<TResult>(\n controller: DeferredTurnController,\n operation: () => Promise<TResult>,\n): Promise<TResult> {\n return activeController.run(controller, operation);\n}\n\nexport function getActiveDeferredTurnController(): DeferredTurnController | undefined {\n return activeController.getStore();\n}\n\nconst MEMORY_HINT_KEYWORDS = ['memory', 'remember', 'recall', 'store', '记忆', '记住', '记下', '回忆', '备忘'];\nconst MEMORY_FALLBACK_HINT =\n 'No matches. 语义记忆工具未启用(需配置 ai.memory.semantic.enabled: true)。'\n + '可使用 write_file 将信息写入 data/memory/sessions/…/MEMORY.md 作为文件记忆替代。';\n\nfunction formatDiscoverResults(items: ReturnType<typeof discoverInCatalog>, query?: string): string {\n if (items.length === 0) {\n if (query && MEMORY_HINT_KEYWORDS.some(keyword => query.toLowerCase().includes(keyword))) {\n return MEMORY_FALLBACK_HINT;\n }\n return 'No matches.';\n }\n return items.map(item => `- [${item.kind}] ${item.name}: ${item.brief}`).join('\\n');\n}\n\nfunction schemaSummary(item: ToolCatalogItem): string {\n const params = item.fullTool.parameters;\n if (!params) return `Loaded tool \"${item.name}\".`;\n return `Loaded tool \"${item.name}\".\\nParameters schema:\\n${JSON.stringify(params, null, 2)}`;\n}\n\nexport function createDeferredTurnController(\n options: DeferredTurnControllerOptions,\n): DeferredTurnController {\n let currentSnapshot = structuredClone(options.snapshot);\n const catalog = [...options.catalog];\n const byName = catalogToolByName(catalog);\n\n const persist = async (next: DeferredToolSessionSnapshot): Promise<void> => {\n currentSnapshot = next;\n await options.persistSnapshot(currentSnapshot);\n };\n\n const isPinned = (name: string): boolean => options.pinnedTools?.has(name) ?? false;\n\n const loadTool = async (name: string): Promise<ToolCatalogItem | undefined> => {\n const item = byName.get(name);\n if (!item) return undefined;\n if (!isPinned(name)) {\n await persist(touchToolInSnapshot(currentSnapshot, name, options.maxLoadedPerSession));\n }\n return item;\n };\n\n const tools: Tool[] = [\n {\n name: 'discover',\n description: 'Search deferred tools and skills by query. kind: tool|skill|all (default all). Empty query returns platform-matched skills.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query (user intent keywords)' },\n kind: { type: 'string', enum: ['tool', 'skill', 'all'], description: 'What to search (default all)' },\n mcp_server: { type: 'string', description: 'Filter tools by MCP server name' },\n },\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const input = args as Record<string, unknown>;\n const kind = (typeof input.kind === 'string' ? input.kind : 'all') as DiscoverKind;\n const query = typeof input.query === 'string' ? input.query : '';\n const mcpServer = typeof input.mcp_server === 'string' ? input.mcp_server : undefined;\n return formatDiscoverResults(discoverInCatalog({\n query,\n kind,\n topK: options.discoverTopK,\n platform: options.platform,\n mcpServer,\n skillRegistry: options.skillRegistry,\n catalog,\n }), query);\n },\n },\n {\n name: 'load_tool',\n description: 'Load a deferred tool schema into this turn so it can be called.',\n parameters: {\n type: 'object',\n properties: { name: { type: 'string', description: 'Tool name from discover results' } },\n required: ['name'],\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const name = String((args as Record<string, unknown>).name);\n const item = await loadTool(name);\n if (!item) return `Tool \"${name}\" not found in catalog.`;\n return `${schemaSummary(item)}\\n${TOOLS_MUTATED_MARKER}`;\n },\n },\n {\n name: 'load_skill',\n description: 'Load full skill instructions and unlock associated tools for this turn.',\n parameters: {\n type: 'object',\n properties: { name: { type: 'string', description: 'Skill name from discover results' } },\n required: ['name'],\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const name = String((args as Record<string, unknown>).name);\n const result = await options.skillInstructions.read(name);\n if (result.status === 'missing') return `Skill '${result.name}' not found. Check skills/ directory.`;\n const { instructions } = result;\n let next = addSkillToSnapshot(currentSnapshot, name);\n const toolNames = options.skillRegistry?.getByName(name)?.tools?.map(tool => tool.name) ?? [];\n next = touchToolsInSnapshot(\n next,\n toolNames.filter(toolName => !isPinned(toolName)),\n options.maxLoadedPerSession,\n );\n await persist(next);\n options.onSkillLoaded?.(name, instructions, toolNames);\n const unlock = toolNames.length > 0 ? `\\nUnlocked tools: ${toolNames.join(', ')}` : '';\n return `${instructions}${unlock}\\n${TOOLS_MUTATED_MARKER}`;\n },\n },\n ];\n\n return Object.freeze({\n tools: Object.freeze(tools),\n snapshot: () => currentSnapshot,\n loadedToolNames: () => getLoadedToolNamesFromSnapshot(currentSnapshot),\n tool: (name: string) => byName.get(name)?.fullTool,\n load: async (name: string) => (await loadTool(name)) !== undefined,\n fork: () => createDeferredTurnController({\n ...options,\n snapshot: currentSnapshot,\n persistSnapshot: async () => undefined,\n }),\n });\n}\n\nexport function isToolsMutatedResult(result: ToolResult): boolean {\n const text = typeof result === 'string' ? result : JSON.stringify(result);\n return text.includes(TOOLS_MUTATED_MARKER);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"deferred-turn-controller.js","sourceRoot":"","sources":["../../src/tool-catalog/deferred-turn-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n addSkillToSnapshot,\n getLoadedToolNamesFromSnapshot,\n touchToolInSnapshot,\n touchToolsInSnapshot,\n type AgentTool,\n type DeferredToolSessionSnapshot,\n} from '@zhin.js/ai';\nimport type { Tool, ToolResult } from '@zhin.js/core';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { SkillRegistry } from '../resource-hub/skill-registry.js';\nimport type { SkillInstructionSource } from '../skill/skill-instruction-reader.js';\nimport { catalogToolByName, discoverInCatalog, type DiscoverKind } from './tool-catalog.js';\nimport type { ToolCatalogItem } from './types.js';\n\nexport const TOOLS_MUTATED_MARKER = '__zhin_tools_mutated__';\n\nexport interface DeferredTurnControllerOptions {\n readonly sessionId: string;\n readonly platform?: string;\n readonly catalog: readonly ToolCatalogItem[];\n readonly skillRegistry: SkillRegistry | null;\n readonly snapshot: DeferredToolSessionSnapshot;\n readonly maxLoadedPerSession: number;\n /** Always-loaded tools: never recorded in the session snapshot, so they never count against or get evicted by `maxLoadedPerSession`. */\n readonly pinnedTools?: ReadonlySet<string>;\n readonly discoverTopK: number;\n readonly persistSnapshot: (snapshot: DeferredToolSessionSnapshot) => Promise<void>;\n readonly onSkillLoaded?: (name: string, instructions: string, toolNames: string[]) => void;\n readonly skillInstructions: SkillInstructionSource;\n}\n\nexport interface DeferredTurnController {\n readonly tools: readonly Tool[];\n snapshot(): DeferredToolSessionSnapshot;\n loadedToolNames(): string[];\n tool(name: string): AgentTool | undefined;\n /** Load a catalog tool into the session. Returns false when the name is not in this turn's catalog. */\n load(name: string): Promise<boolean>;\n fork(): DeferredTurnController;\n}\n\nconst activeController = new AsyncLocalStorage<DeferredTurnController>();\n\nexport function runWithDeferredTurnController<TResult>(\n controller: DeferredTurnController,\n operation: () => Promise<TResult>,\n): Promise<TResult> {\n return activeController.run(controller, operation);\n}\n\nexport function getActiveDeferredTurnController(): DeferredTurnController | undefined {\n return activeController.getStore();\n}\n\nconst MEMORY_HINT_KEYWORDS = ['memory', 'remember', 'recall', 'store', '记忆', '记住', '记下', '回忆', '备忘'];\nconst MEMORY_FALLBACK_HINT =\n 'No matches. 语义记忆工具未启用(需配置 ai.memory.semantic.enabled: true)。'\n + '可使用 write_file 将信息写入 data/memory/sessions/…/MEMORY.md 作为文件记忆替代。';\n\nfunction formatDiscoverResults(items: ReturnType<typeof discoverInCatalog>, query?: string): string {\n if (items.length === 0) {\n if (query && MEMORY_HINT_KEYWORDS.some(keyword => query.toLowerCase().includes(keyword))) {\n return MEMORY_FALLBACK_HINT;\n }\n return 'No matches.';\n }\n return items.map(item => `- [${item.kind}] ${item.name}: ${item.brief}`).join('\\n');\n}\n\nfunction schemaSummary(item: ToolCatalogItem): string {\n return `Loaded tool \"${item.name}\". Use its tool definition for arguments.`;\n}\n\nexport function createDeferredTurnController(\n options: DeferredTurnControllerOptions,\n): DeferredTurnController {\n let currentSnapshot = structuredClone(options.snapshot);\n const catalog = [...options.catalog];\n const byName = catalogToolByName(catalog);\n\n const persist = async (next: DeferredToolSessionSnapshot): Promise<void> => {\n currentSnapshot = next;\n await options.persistSnapshot(currentSnapshot);\n };\n\n const isPinned = (name: string): boolean => options.pinnedTools?.has(name) ?? false;\n\n const loadTool = async (name: string): Promise<ToolCatalogItem | undefined> => {\n const item = byName.get(name);\n if (!item) return undefined;\n if (!isPinned(name)) {\n await persist(touchToolInSnapshot(currentSnapshot, name, options.maxLoadedPerSession));\n }\n return item;\n };\n\n const tools: Tool[] = [\n {\n name: 'discover',\n description: 'Search deferred tools and skills by query. kind: tool|skill|all (default all). Empty query returns platform-matched skills.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query (user intent keywords)' },\n kind: { type: 'string', enum: ['tool', 'skill', 'all'], description: 'What to search (default all)' },\n mcp_server: { type: 'string', description: 'Filter tools by MCP server name' },\n },\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const input = args as Record<string, unknown>;\n const kind = (typeof input.kind === 'string' ? input.kind : 'all') as DiscoverKind;\n const query = typeof input.query === 'string' ? input.query : '';\n const mcpServer = typeof input.mcp_server === 'string' ? input.mcp_server : undefined;\n return formatDiscoverResults(discoverInCatalog({\n query,\n kind,\n topK: options.discoverTopK,\n platform: options.platform,\n mcpServer,\n skillRegistry: options.skillRegistry,\n catalog,\n }), query);\n },\n },\n {\n name: 'load_tool',\n description: 'Load a deferred tool schema into this turn so it can be called.',\n parameters: {\n type: 'object',\n properties: { name: { type: 'string', description: 'Tool name from discover results' } },\n required: ['name'],\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const name = String((args as Record<string, unknown>).name);\n const item = await loadTool(name);\n if (!item) return `Tool \"${name}\" not found in catalog.`;\n return `${schemaSummary(item)}\\n${TOOLS_MUTATED_MARKER}`;\n },\n },\n {\n name: 'load_skill',\n description: 'Load full skill instructions and unlock associated tools for this turn.',\n parameters: {\n type: 'object',\n properties: { name: { type: 'string', description: 'Skill name from discover results' } },\n required: ['name'],\n },\n source: 'builtin:agent',\n execute: async (args): Promise<ToolResult> => {\n const name = String((args as Record<string, unknown>).name);\n const result = await options.skillInstructions.read(name);\n if (result.status === 'missing') return `Skill '${result.name}' not found. Check skills/ directory.`;\n const { instructions } = result;\n let next = addSkillToSnapshot(currentSnapshot, name);\n const toolNames = options.skillRegistry?.getByName(name)?.tools?.map(tool => tool.name) ?? [];\n next = touchToolsInSnapshot(\n next,\n toolNames.filter(toolName => !isPinned(toolName)),\n options.maxLoadedPerSession,\n );\n await persist(next);\n options.onSkillLoaded?.(name, instructions, toolNames);\n const unlock = toolNames.length > 0 ? `\\nUnlocked tools: ${toolNames.join(', ')}` : '';\n return `${instructions}${unlock}\\n${TOOLS_MUTATED_MARKER}`;\n },\n },\n ];\n\n return Object.freeze({\n tools: Object.freeze(tools),\n snapshot: () => currentSnapshot,\n loadedToolNames: () => getLoadedToolNamesFromSnapshot(currentSnapshot),\n tool: (name: string) => byName.get(name)?.fullTool,\n load: async (name: string) => (await loadTool(name)) !== undefined,\n fork: () => createDeferredTurnController({\n ...options,\n snapshot: currentSnapshot,\n persistSnapshot: async () => undefined,\n }),\n });\n}\n\nexport function isToolsMutatedResult(result: ToolResult): boolean {\n const text = typeof result === 'string' ? result : JSON.stringify(result);\n return text.includes(TOOLS_MUTATED_MARKER);\n}\n"]}
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { type AgentMessage, type AgentTool } from '@zhin.js/ai';
|
|
11
11
|
import { estimatePromptTokens } from '../prompt/estimate-tokens.js';
|
|
12
12
|
/** Prompt segments, in the order the report lists them. */
|
|
13
|
-
export declare const CONTEXT_BUDGET_SEGMENTS: readonly ["system", "tool_schemas", "tool_index", "skill_catalog", "agent_catalog", "active_skills", "memory", "bootstrap", "history", "user"];
|
|
13
|
+
export declare const CONTEXT_BUDGET_SEGMENTS: readonly ["system", "tool_schemas", "output_schema", "tool_index", "skill_catalog", "agent_catalog", "active_skills", "memory", "bootstrap", "history", "user"];
|
|
14
14
|
export type ContextBudgetSegment = (typeof CONTEXT_BUDGET_SEGMENTS)[number];
|
|
15
|
-
/**
|
|
15
|
+
/** Fixed-cost categories; a catalog carried in turn data is deducted separately. */
|
|
16
16
|
export declare const FIXED_CONTEXT_SEGMENTS: ReadonlySet<ContextBudgetSegment>;
|
|
17
17
|
export { estimatePromptTokens };
|
|
18
18
|
export interface ContextBudgetSection {
|
|
@@ -22,7 +22,7 @@ export interface ContextBudgetSection {
|
|
|
22
22
|
/** Snake_case: persisted inside the TurnTrace JSON. */
|
|
23
23
|
export interface ContextBudgetReport {
|
|
24
24
|
readonly total_tokens: number;
|
|
25
|
-
/**
|
|
25
|
+
/** Fixed segments, excluding the current user-context tool catalog. */
|
|
26
26
|
readonly fixed_tokens: number;
|
|
27
27
|
readonly segments: Readonly<Record<ContextBudgetSegment, number>>;
|
|
28
28
|
/** System prompt sections, largest first. */
|
|
@@ -35,6 +35,7 @@ export interface ContextBudgetReport {
|
|
|
35
35
|
export interface ContextBudgetInput {
|
|
36
36
|
/** The final system prompt sent to the model. */
|
|
37
37
|
readonly systemPrompt: string;
|
|
38
|
+
readonly outputSchema?: Record<string, unknown>;
|
|
38
39
|
/** Per-section text sizes from the prompt assembly (ids such as `§8_skills`). */
|
|
39
40
|
readonly sections?: readonly Readonly<{
|
|
40
41
|
id: string;
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* provider's real `prompt_tokens` stays in the trace's model calls for
|
|
8
8
|
* comparison. Only sizes are kept; prompt text never enters the report.
|
|
9
9
|
*/
|
|
10
|
-
import { estimateAgentMessagesTokens } from '@zhin.js/ai';
|
|
10
|
+
import { projectPromptContext, estimateAgentMessagesTokens } from '@zhin.js/ai';
|
|
11
11
|
import { estimatePromptTokens } from '../prompt/estimate-tokens.js';
|
|
12
12
|
import { AGENT_CATALOG_HEADING, DEFERRED_TOOL_INDEX_HEADING, SKILL_CATALOG_HEADING } from '../skill/capability-catalog.js';
|
|
13
13
|
/** Prompt segments, in the order the report lists them. */
|
|
14
14
|
export const CONTEXT_BUDGET_SEGMENTS = [
|
|
15
15
|
'system',
|
|
16
16
|
'tool_schemas',
|
|
17
|
+
'output_schema',
|
|
17
18
|
'tool_index',
|
|
18
19
|
'skill_catalog',
|
|
19
20
|
'agent_catalog',
|
|
@@ -23,10 +24,11 @@ export const CONTEXT_BUDGET_SEGMENTS = [
|
|
|
23
24
|
'history',
|
|
24
25
|
'user',
|
|
25
26
|
];
|
|
26
|
-
/**
|
|
27
|
+
/** Fixed-cost categories; a catalog carried in turn data is deducted separately. */
|
|
27
28
|
export const FIXED_CONTEXT_SEGMENTS = new Set([
|
|
28
29
|
'system',
|
|
29
30
|
'tool_schemas',
|
|
31
|
+
'output_schema',
|
|
30
32
|
'tool_index',
|
|
31
33
|
'skill_catalog',
|
|
32
34
|
'agent_catalog',
|
|
@@ -78,22 +80,40 @@ export function buildContextBudgetReport(input) {
|
|
|
78
80
|
segments.agent_catalog += attributed - Math.min(skillCatalog, attributed);
|
|
79
81
|
segments.bootstrap -= attributed;
|
|
80
82
|
}
|
|
81
|
-
//
|
|
83
|
+
// Legacy callers may still carry the tool index in system; current turns mark it in user context.
|
|
82
84
|
segments.tool_index = estimatePromptTokens(headedBlock(input.systemPrompt, DEFERRED_TOOL_INDEX_HEADING));
|
|
83
85
|
const attributedSystem = segments.tool_index + segments.skill_catalog + segments.agent_catalog + segments.memory + segments.bootstrap;
|
|
84
86
|
segments.system = Math.max(0, systemTokens - attributedSystem);
|
|
85
87
|
segments.tool_schemas = toolSchemaTokens(input.loadedTools);
|
|
86
|
-
segments.
|
|
87
|
-
const
|
|
88
|
-
segments.
|
|
89
|
-
|
|
88
|
+
segments.output_schema = input.outputSchema ? estimatePromptTokens(JSON.stringify(input.outputSchema)) : 0;
|
|
89
|
+
const projected = projectPromptContext([...input.historyMessages, ...input.userMessages]);
|
|
90
|
+
segments.history = estimateAgentMessagesTokens(projected.slice(0, input.historyMessages.length));
|
|
91
|
+
const current = projected.slice(input.historyMessages.length);
|
|
92
|
+
const contextTokens = (key) => input.userMessages.reduce((total, message, index) => {
|
|
93
|
+
const renderedContent = current[index]?.content;
|
|
94
|
+
if (!Array.isArray(message.content) || !Array.isArray(renderedContent))
|
|
95
|
+
return total;
|
|
96
|
+
return total + message.content.reduce((sum, block, blockIndex) => {
|
|
97
|
+
const rendered = renderedContent[blockIndex];
|
|
98
|
+
return sum + (typeof block.contextRef?.key === 'string' && key(block.contextRef.key) && rendered?.type === 'text'
|
|
99
|
+
? estimatePromptTokens(rendered.text) : 0);
|
|
100
|
+
}, 0);
|
|
101
|
+
}, 0);
|
|
102
|
+
const dynamicIndexTokens = contextTokens(key => key === 'deferred-tools');
|
|
103
|
+
segments.tool_index += dynamicIndexTokens;
|
|
104
|
+
const userTokens = estimateAgentMessagesTokens(current);
|
|
105
|
+
const skillTokens = contextTokens(key => key.startsWith('skill:'));
|
|
106
|
+
const hasMarkedSkills = input.userMessages.some(message => Array.isArray(message.content)
|
|
107
|
+
&& message.content.some(block => typeof block.contextRef?.key === 'string' && block.contextRef.key.startsWith('skill:')));
|
|
108
|
+
segments.active_skills = Math.min(hasMarkedSkills ? skillTokens : estimatePromptTokens(input.activeSkillsContext ?? ''), userTokens);
|
|
109
|
+
segments.user = Math.max(0, userTokens - segments.active_skills - dynamicIndexTokens);
|
|
90
110
|
const total = CONTEXT_BUDGET_SEGMENTS.reduce((sum, segment) => sum + segments[segment], 0);
|
|
91
111
|
const fixed = CONTEXT_BUDGET_SEGMENTS
|
|
92
112
|
.filter((segment) => FIXED_CONTEXT_SEGMENTS.has(segment))
|
|
93
113
|
.reduce((sum, segment) => sum + segments[segment], 0);
|
|
94
114
|
return Object.freeze({
|
|
95
115
|
total_tokens: total,
|
|
96
|
-
fixed_tokens: fixed,
|
|
116
|
+
fixed_tokens: fixed - dynamicIndexTokens,
|
|
97
117
|
segments: Object.freeze(segments),
|
|
98
118
|
sections: Object.freeze(sections),
|
|
99
119
|
tools: Object.freeze({ loaded: input.loadedTools.length, deferred: input.deferredToolCount }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget.js","sourceRoot":"","sources":["../../src/trace/context-budget.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context budget report (#729, M2.1): how many prompt tokens each part of a\n * turn's first model request costs, so context work is driven by data.\n *\n * Numbers are estimates (CJK ≈ 1 token per character, other text ≈ 4\n * characters per token), computed from what the harness actually sends. The\n * provider's real `prompt_tokens` stays in the trace's model calls for\n * comparison. Only sizes are kept; prompt text never enters the report.\n */\nimport { estimateAgentMessagesTokens, type AgentMessage, type AgentTool } from '@zhin.js/ai';\nimport { estimatePromptTokens } from '../prompt/estimate-tokens.js';\nimport { AGENT_CATALOG_HEADING, DEFERRED_TOOL_INDEX_HEADING, SKILL_CATALOG_HEADING } from '../skill/capability-catalog.js';\n\n/** Prompt segments, in the order the report lists them. */\nexport const CONTEXT_BUDGET_SEGMENTS = [\n 'system',\n 'tool_schemas',\n 'tool_index',\n 'skill_catalog',\n 'agent_catalog',\n 'active_skills',\n 'memory',\n 'bootstrap',\n 'history',\n 'user',\n] as const;\n\nexport type ContextBudgetSegment = (typeof CONTEXT_BUDGET_SEGMENTS)[number];\n\n/** Segments that are paid on every request regardless of the conversation. */\nexport const FIXED_CONTEXT_SEGMENTS: ReadonlySet<ContextBudgetSegment> = new Set([\n 'system',\n 'tool_schemas',\n 'tool_index',\n 'skill_catalog',\n 'agent_catalog',\n 'memory',\n 'bootstrap',\n]);\n\nexport { estimatePromptTokens };\n\nexport interface ContextBudgetSection {\n readonly id: string;\n readonly tokens: number;\n}\n\n/** Snake_case: persisted inside the TurnTrace JSON. */\nexport interface ContextBudgetReport {\n readonly total_tokens: number;\n /** Sum of the segments in FIXED_CONTEXT_SEGMENTS. */\n readonly fixed_tokens: number;\n readonly segments: Readonly<Record<ContextBudgetSegment, number>>;\n /** System prompt sections, largest first. */\n readonly sections: readonly ContextBudgetSection[];\n readonly tools: Readonly<{ loaded: number; deferred: number }>;\n}\n\nexport interface ContextBudgetInput {\n /** The final system prompt sent to the model. */\n readonly systemPrompt: string;\n /** Per-section text sizes from the prompt assembly (ids such as `§8_skills`). */\n readonly sections?: readonly Readonly<{ id: string; approxChars: number; approxTokens?: number }>[];\n /** Bootstrap text that went into the system prompt; capability catalogs are attributed out of it. */\n readonly bootstrapContext?: string;\n /** Loaded skill instructions; the turn envelope carries them inside the user message. */\n readonly activeSkillsContext?: string;\n /** Tools whose full schema is sent on the request. */\n readonly loadedTools: readonly AgentTool[];\n /** Number of catalog tools reachable only via discover / load_tool. */\n readonly deferredToolCount: number;\n readonly historyMessages: readonly AgentMessage[];\n readonly userMessages: readonly AgentMessage[];\n}\n\nconst SECTION_SEGMENT: Readonly<Record<string, ContextBudgetSegment>> = {\n '§8_skills': 'skill_catalog',\n '§5e_memory_instructions': 'memory',\n '§11_bootstrap': 'bootstrap',\n};\n\nfunction toolSchemaTokens(tools: readonly AgentTool[]): number {\n return tools.reduce((sum, tool) => sum + estimatePromptTokens(JSON.stringify({\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n })), 0);\n}\n\n/** Text of the block that starts at `heading` and runs to the next `## ` heading. */\nfunction headedBlock(text: string, heading: string): string {\n const start = text.indexOf(heading);\n if (start < 0) return '';\n const next = text.indexOf('\\n## ', start + heading.length);\n return text.slice(start, next < 0 ? text.length : next);\n}\n\nfunction sectionTokens(section: Readonly<{ approxChars: number; approxTokens?: number }>): number {\n return section.approxTokens ?? Math.ceil(section.approxChars / 4);\n}\n\nexport function buildContextBudgetReport(input: ContextBudgetInput): ContextBudgetReport {\n const systemTokens = estimatePromptTokens(input.systemPrompt);\n const sections = (input.sections ?? [])\n .map((section) => Object.freeze({ id: section.id, tokens: sectionTokens(section) }))\n .sort((left, right) => right.tokens - left.tokens);\n\n const segments: Record<ContextBudgetSegment, number> = Object.fromEntries(\n CONTEXT_BUDGET_SEGMENTS.map((segment) => [segment, 0]),\n ) as Record<ContextBudgetSegment, number>;\n\n for (const section of sections) {\n const segment = SECTION_SEGMENT[section.id];\n if (segment) segments[segment] += section.tokens;\n }\n\n // The plugin runtime renders skill/agent catalogs into the bootstrap block.\n const bootstrap = input.bootstrapContext ?? '';\n if (bootstrap && segments.bootstrap > 0) {\n const skillCatalog = estimatePromptTokens(headedBlock(bootstrap, SKILL_CATALOG_HEADING));\n const agentCatalog = estimatePromptTokens(headedBlock(bootstrap, AGENT_CATALOG_HEADING));\n const attributed = Math.min(segments.bootstrap, skillCatalog + agentCatalog);\n segments.skill_catalog += Math.min(skillCatalog, attributed);\n segments.agent_catalog += attributed - Math.min(skillCatalog, attributed);\n segments.bootstrap -= attributed;\n }\n\n // The deferred tool index is appended to the system prompt after assembly.\n segments.tool_index = estimatePromptTokens(headedBlock(input.systemPrompt, DEFERRED_TOOL_INDEX_HEADING));\n\n const attributedSystem = segments.tool_index + segments.skill_catalog + segments.agent_catalog + segments.memory + segments.bootstrap;\n segments.system = Math.max(0, systemTokens - attributedSystem);\n segments.tool_schemas = toolSchemaTokens(input.loadedTools);\n segments.history = estimateAgentMessagesTokens([...input.historyMessages]);\n\n const userTokens = estimateAgentMessagesTokens([...input.userMessages]);\n segments.active_skills = Math.min(estimatePromptTokens(input.activeSkillsContext ?? ''), userTokens);\n segments.user = userTokens - segments.active_skills;\n\n const total = CONTEXT_BUDGET_SEGMENTS.reduce((sum, segment) => sum + segments[segment], 0);\n const fixed = CONTEXT_BUDGET_SEGMENTS\n .filter((segment) => FIXED_CONTEXT_SEGMENTS.has(segment))\n .reduce((sum, segment) => sum + segments[segment], 0);\n\n return Object.freeze({\n total_tokens: total,\n fixed_tokens: fixed,\n segments: Object.freeze(segments),\n sections: Object.freeze(sections),\n tools: Object.freeze({ loaded: input.loadedTools.length, deferred: input.deferredToolCount }),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-budget.js","sourceRoot":"","sources":["../../src/trace/context-budget.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Context budget report (#729, M2.1): how many prompt tokens each part of a\n * turn's first model request costs, so context work is driven by data.\n *\n * Numbers are estimates (CJK ≈ 1 token per character, other text ≈ 4\n * characters per token), computed from what the harness actually sends. The\n * provider's real `prompt_tokens` stays in the trace's model calls for\n * comparison. Only sizes are kept; prompt text never enters the report.\n */\nimport { projectPromptContext, estimateAgentMessagesTokens, type AgentMessage, type AgentTool } from '@zhin.js/ai';\nimport { estimatePromptTokens } from '../prompt/estimate-tokens.js';\nimport { AGENT_CATALOG_HEADING, DEFERRED_TOOL_INDEX_HEADING, SKILL_CATALOG_HEADING } from '../skill/capability-catalog.js';\n\n/** Prompt segments, in the order the report lists them. */\nexport const CONTEXT_BUDGET_SEGMENTS = [\n 'system',\n 'tool_schemas',\n 'output_schema',\n 'tool_index',\n 'skill_catalog',\n 'agent_catalog',\n 'active_skills',\n 'memory',\n 'bootstrap',\n 'history',\n 'user',\n] as const;\n\nexport type ContextBudgetSegment = (typeof CONTEXT_BUDGET_SEGMENTS)[number];\n\n/** Fixed-cost categories; a catalog carried in turn data is deducted separately. */\nexport const FIXED_CONTEXT_SEGMENTS: ReadonlySet<ContextBudgetSegment> = new Set([\n 'system',\n 'tool_schemas',\n 'output_schema',\n 'tool_index',\n 'skill_catalog',\n 'agent_catalog',\n 'memory',\n 'bootstrap',\n]);\n\nexport { estimatePromptTokens };\n\nexport interface ContextBudgetSection {\n readonly id: string;\n readonly tokens: number;\n}\n\n/** Snake_case: persisted inside the TurnTrace JSON. */\nexport interface ContextBudgetReport {\n readonly total_tokens: number;\n /** Fixed segments, excluding the current user-context tool catalog. */\n readonly fixed_tokens: number;\n readonly segments: Readonly<Record<ContextBudgetSegment, number>>;\n /** System prompt sections, largest first. */\n readonly sections: readonly ContextBudgetSection[];\n readonly tools: Readonly<{ loaded: number; deferred: number }>;\n}\n\nexport interface ContextBudgetInput {\n /** The final system prompt sent to the model. */\n readonly systemPrompt: string;\n readonly outputSchema?: Record<string, unknown>;\n /** Per-section text sizes from the prompt assembly (ids such as `§8_skills`). */\n readonly sections?: readonly Readonly<{ id: string; approxChars: number; approxTokens?: number }>[];\n /** Bootstrap text that went into the system prompt; capability catalogs are attributed out of it. */\n readonly bootstrapContext?: string;\n /** Loaded skill instructions; the turn envelope carries them inside the user message. */\n readonly activeSkillsContext?: string;\n /** Tools whose full schema is sent on the request. */\n readonly loadedTools: readonly AgentTool[];\n /** Number of catalog tools reachable only via discover / load_tool. */\n readonly deferredToolCount: number;\n readonly historyMessages: readonly AgentMessage[];\n readonly userMessages: readonly AgentMessage[];\n}\n\nconst SECTION_SEGMENT: Readonly<Record<string, ContextBudgetSegment>> = {\n '§8_skills': 'skill_catalog',\n '§5e_memory_instructions': 'memory',\n '§11_bootstrap': 'bootstrap',\n};\n\nfunction toolSchemaTokens(tools: readonly AgentTool[]): number {\n return tools.reduce((sum, tool) => sum + estimatePromptTokens(JSON.stringify({\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n })), 0);\n}\n\n/** Text of the block that starts at `heading` and runs to the next `## ` heading. */\nfunction headedBlock(text: string, heading: string): string {\n const start = text.indexOf(heading);\n if (start < 0) return '';\n const next = text.indexOf('\\n## ', start + heading.length);\n return text.slice(start, next < 0 ? text.length : next);\n}\n\nfunction sectionTokens(section: Readonly<{ approxChars: number; approxTokens?: number }>): number {\n return section.approxTokens ?? Math.ceil(section.approxChars / 4);\n}\n\nexport function buildContextBudgetReport(input: ContextBudgetInput): ContextBudgetReport {\n const systemTokens = estimatePromptTokens(input.systemPrompt);\n const sections = (input.sections ?? [])\n .map((section) => Object.freeze({ id: section.id, tokens: sectionTokens(section) }))\n .sort((left, right) => right.tokens - left.tokens);\n\n const segments: Record<ContextBudgetSegment, number> = Object.fromEntries(\n CONTEXT_BUDGET_SEGMENTS.map((segment) => [segment, 0]),\n ) as Record<ContextBudgetSegment, number>;\n\n for (const section of sections) {\n const segment = SECTION_SEGMENT[section.id];\n if (segment) segments[segment] += section.tokens;\n }\n\n // The plugin runtime renders skill/agent catalogs into the bootstrap block.\n const bootstrap = input.bootstrapContext ?? '';\n if (bootstrap && segments.bootstrap > 0) {\n const skillCatalog = estimatePromptTokens(headedBlock(bootstrap, SKILL_CATALOG_HEADING));\n const agentCatalog = estimatePromptTokens(headedBlock(bootstrap, AGENT_CATALOG_HEADING));\n const attributed = Math.min(segments.bootstrap, skillCatalog + agentCatalog);\n segments.skill_catalog += Math.min(skillCatalog, attributed);\n segments.agent_catalog += attributed - Math.min(skillCatalog, attributed);\n segments.bootstrap -= attributed;\n }\n\n // Legacy callers may still carry the tool index in system; current turns mark it in user context.\n segments.tool_index = estimatePromptTokens(headedBlock(input.systemPrompt, DEFERRED_TOOL_INDEX_HEADING));\n\n const attributedSystem = segments.tool_index + segments.skill_catalog + segments.agent_catalog + segments.memory + segments.bootstrap;\n segments.system = Math.max(0, systemTokens - attributedSystem);\n segments.tool_schemas = toolSchemaTokens(input.loadedTools);\n segments.output_schema = input.outputSchema ? estimatePromptTokens(JSON.stringify(input.outputSchema)) : 0;\n const projected = projectPromptContext([...input.historyMessages, ...input.userMessages]);\n segments.history = estimateAgentMessagesTokens(projected.slice(0, input.historyMessages.length));\n const current = projected.slice(input.historyMessages.length);\n const contextTokens = (key: (key: string) => boolean) => input.userMessages.reduce((total, message, index) => {\n const renderedContent = current[index]?.content;\n if (!Array.isArray(message.content) || !Array.isArray(renderedContent)) return total;\n return total + message.content.reduce((sum: number, block: { contextRef?: { key: string } }, blockIndex: number) => {\n const rendered = renderedContent[blockIndex];\n return sum + (typeof block.contextRef?.key === 'string' && key(block.contextRef.key) && rendered?.type === 'text'\n ? estimatePromptTokens(rendered.text) : 0);\n }, 0);\n }, 0);\n const dynamicIndexTokens = contextTokens(key => key === 'deferred-tools');\n segments.tool_index += dynamicIndexTokens;\n const userTokens = estimateAgentMessagesTokens(current);\n const skillTokens = contextTokens(key => key.startsWith('skill:'));\n const hasMarkedSkills = input.userMessages.some(message => Array.isArray(message.content)\n && message.content.some(block => typeof block.contextRef?.key === 'string' && block.contextRef.key.startsWith('skill:')));\n segments.active_skills = Math.min(hasMarkedSkills ? skillTokens : estimatePromptTokens(input.activeSkillsContext ?? ''), userTokens);\n segments.user = Math.max(0, userTokens - segments.active_skills - dynamicIndexTokens);\n\n const total = CONTEXT_BUDGET_SEGMENTS.reduce((sum, segment) => sum + segments[segment], 0);\n const fixed = CONTEXT_BUDGET_SEGMENTS\n .filter((segment) => FIXED_CONTEXT_SEGMENTS.has(segment))\n .reduce((sum, segment) => sum + segments[segment], 0);\n\n return Object.freeze({\n total_tokens: total,\n fixed_tokens: fixed - dynamicIndexTokens,\n segments: Object.freeze(segments),\n sections: Object.freeze(sections),\n tools: Object.freeze({ loaded: input.loadedTools.length, deferred: input.deferredToolCount }),\n });\n}\n"]}
|
|
@@ -56,7 +56,9 @@ function renderTrace(trace, indent, withTimeline) {
|
|
|
56
56
|
for (const call of trace.model_calls) {
|
|
57
57
|
const cache = call.cache_read_tokens ? ` cache=${call.cache_read_tokens}` : '';
|
|
58
58
|
const reasoning = call.reasoning_tokens ? ` reasoning=${call.reasoning_tokens}` : '';
|
|
59
|
-
|
|
59
|
+
const cacheWrite = call.cache_write_tokens !== undefined ? ` cache_write=${call.cache_write_tokens}` : '';
|
|
60
|
+
const model = call.model ? ` model=${call.provider ?? '?'}/${call.model}` : '';
|
|
61
|
+
lines.push(`${indent} #${call.index} in=${call.prompt_tokens} out=${call.completion_tokens}${cache}${reasoning}${cacheWrite}${model}`);
|
|
60
62
|
}
|
|
61
63
|
if (trace.context_budget)
|
|
62
64
|
lines.push(...renderContextBudget(trace.context_budget, indent));
|
|
@@ -95,6 +97,7 @@ function renderTrace(trace, indent, withTimeline) {
|
|
|
95
97
|
const SEGMENT_LABEL = {
|
|
96
98
|
system: '系统提示',
|
|
97
99
|
tool_schemas: '工具 schema',
|
|
100
|
+
output_schema: '输出 schema',
|
|
98
101
|
tool_index: '按需工具索引',
|
|
99
102
|
skill_catalog: '技能目录',
|
|
100
103
|
agent_catalog: '专家 agent 目录',
|