@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.0.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/LICENSE +201 -0
- package/NOTICE +1 -0
- package/README.md +124 -2
- package/THIRD_PARTY_NOTICES.md +66 -0
- package/ZCode-LICENSE +202 -0
- package/dist/index.mjs +4871 -0
- package/dist/native-command-plan.LICENSE.txt +221 -0
- package/dist/native-command-plan.json +16 -0
- package/dist/native-command-plan.mjs +8 -0
- package/dist/rpc-entry.mjs +5682 -0
- package/package.json +169 -4
- package/src/native/a2a-receipt-source.mjs +19 -0
- package/src/native/a2a-tools.mjs +517 -0
- package/src/native/activity-status.mjs +30 -0
- package/src/native/artifact-codec.mjs +481 -0
- package/src/native/artifact-store.mjs +1162 -0
- package/src/native/assistant-stream.mjs +76 -0
- package/src/native/background-command-port.mjs +95 -0
- package/src/native/browser-action-observation.mjs +43 -0
- package/src/native/browser-availability.mjs +22 -0
- package/src/native/browser-canvas-metrics.mjs +71 -0
- package/src/native/browser-dom.mjs +193 -0
- package/src/native/browser-playwright.mjs +1249 -0
- package/src/native/browser-vision.mjs +369 -0
- package/src/native/browser-workspace-policy.mjs +22 -0
- package/src/native/browser-workspace-protocol.mjs +95 -0
- package/src/native/browser-workspace-server.mjs +164 -0
- package/src/native/browser-workspace.mjs +355 -0
- package/src/native/captured-source.mjs +3 -0
- package/src/native/child-budget-allocation.mjs +87 -0
- package/src/native/child-capabilities.mjs +35 -0
- package/src/native/child-command-resource.mjs +20 -0
- package/src/native/child-history.mjs +188 -0
- package/src/native/child-merge.mjs +98 -0
- package/src/native/child-model-surface.mjs +19 -0
- package/src/native/child-observation.mjs +100 -0
- package/src/native/child-owner.mjs +620 -0
- package/src/native/child-publication.mjs +76 -0
- package/src/native/child-readers.mjs +366 -0
- package/src/native/child-tools.mjs +46 -0
- package/src/native/child-workspace-binding.mjs +29 -0
- package/src/native/child-workspace-tools.mjs +1 -0
- package/src/native/child-workspaces.mjs +655 -0
- package/src/native/children.mjs +365 -0
- package/src/native/command-plan-worker.mjs +14 -0
- package/src/native/command-plan.mjs +31 -0
- package/src/native/command-progress.mjs +64 -0
- package/src/native/command-shell.mjs +9 -0
- package/src/native/command-workspace-observation.mjs +36 -0
- package/src/native/component-tools.mjs +280 -0
- package/src/native/context-counter.mjs +133 -0
- package/src/native/context-markers.mjs +51 -0
- package/src/native/context-profile.mjs +72 -0
- package/src/native/context-status.mjs +27 -0
- package/src/native/conversation-command-control.mjs +133 -0
- package/src/native/delivery-timeline.mjs +39 -0
- package/src/native/dsh-background-ownership.mjs +102 -0
- package/src/native/dsh-managed-ownership.mjs +23 -0
- package/src/native/dsh-turn-contract.mjs +3 -0
- package/src/native/dsh-turn-ownership.mjs +54 -0
- package/src/native/dsh-turn-runtime.mjs +498 -0
- package/src/native/e2b-background-supervisor.mjs +347 -0
- package/src/native/e2b-background.mjs +368 -0
- package/src/native/e2b-command-state.mjs +40 -0
- package/src/native/e2b-command-tools.mjs +486 -0
- package/src/native/e2b-sandbox-owner.mjs +227 -0
- package/src/native/hook-executor.mjs +249 -0
- package/src/native/instruction-assembly.mjs +60 -0
- package/src/native/instruction-context.mjs +54 -0
- package/src/native/local-background-supervisor.mjs +108 -0
- package/src/native/local-background.mjs +207 -0
- package/src/native/local-command-evidence.mjs +57 -0
- package/src/native/local-command-policy-worker.mjs +197 -0
- package/src/native/local-command-policy.mjs +46 -0
- package/src/native/local-command-state.mjs +177 -0
- package/src/native/local-command-tools.mjs +302 -0
- package/src/native/local-execution-profile.mjs +36 -0
- package/src/native/local-output-policy.mjs +92 -0
- package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
- package/src/native/local-process-bin/darwin-x64/launcher +0 -0
- package/src/native/local-process-bin/linux-arm64/launcher +0 -0
- package/src/native/local-process-bin/linux-x64/launcher +0 -0
- package/src/native/local-process-launcher.c +183 -0
- package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
- package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
- package/src/native/local-process-licenses/NOTICE +21 -0
- package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
- package/src/native/local-process-manifest.json +32 -0
- package/src/native/local-process.mjs +246 -0
- package/src/native/local-resource-profile.mjs +32 -0
- package/src/native/memory-candidate.mjs +121 -0
- package/src/native/memory-extraction-run.mjs +60 -0
- package/src/native/memory-extraction-tools.mjs +116 -0
- package/src/native/memory-recall.mjs +65 -0
- package/src/native/memory-store.mjs +334 -0
- package/src/native/memory-tools.mjs +329 -0
- package/src/native/model-anthropic-cache.mjs +49 -0
- package/src/native/model-anthropic-tool-search.mjs +85 -0
- package/src/native/model-anthropic.mjs +753 -0
- package/src/native/model-chat-usage.mjs +24 -0
- package/src/native/model-inline-reasoning.mjs +111 -0
- package/src/native/model-media.mjs +174 -0
- package/src/native/model-normalize.mjs +228 -0
- package/src/native/model-openai-chat.mjs +561 -0
- package/src/native/model-port.mjs +361 -0
- package/src/native/model-provider-compat.mjs +129 -0
- package/src/native/model-replay.mjs +153 -0
- package/src/native/model-request.mjs +15 -0
- package/src/native/model-responses.mjs +1205 -0
- package/src/native/model-sse.mjs +171 -0
- package/src/native/model-surface-host.mjs +27 -0
- package/src/native/model-tool-search-policy.mjs +16 -0
- package/src/native/model-transport-observation.mjs +27 -0
- package/src/native/owned-child.mjs +114 -0
- package/src/native/owned-command.mjs +106 -0
- package/src/native/python-syntax-worker.mjs +60 -0
- package/src/native/python-syntax.mjs +50 -0
- package/src/native/read-interruption.mjs +16 -0
- package/src/native/result-stream.mjs +63 -0
- package/src/native/ruff-ast.mjs +95 -0
- package/src/native/run-observation.mjs +128 -0
- package/src/native/session-delivery-journal.mjs +96 -0
- package/src/native/session-delivery.mjs +434 -0
- package/src/native/shell-analysis.mjs +50 -0
- package/src/native/skill-disclosure.mjs +404 -0
- package/src/native/skill-events.mjs +164 -0
- package/src/native/skill-tools.mjs +813 -0
- package/src/native/source-page.mjs +73 -0
- package/src/native/sqlite-session-inbox.mjs +42 -0
- package/src/native/sqlite-store.mjs +617 -0
- package/src/native/tool-authority.mjs +99 -0
- package/src/native/tool-guidance.mjs +12 -0
- package/src/native/tool-observation.mjs +75 -0
- package/src/native/tool-states.mjs +14 -0
- package/src/native/verification-authority.mjs +37 -0
- package/src/native/web-tools.mjs +643 -0
- package/src/native/workspace-checkpoints.mjs +312 -0
- package/src/native/workspace-context.mjs +61 -0
- package/src/native/workspace-display.mjs +39 -0
- package/src/native/workspace-edit-match.mjs +19 -0
- package/src/native/workspace-edit-worker.mjs +15 -0
- package/src/native/workspace-editing.mjs +447 -0
- package/src/native/workspace-image.mjs +72 -0
- package/src/native/workspace-instructions.mjs +107 -0
- package/src/native/workspace-mutation-lock.mjs +74 -0
- package/src/native/workspace-patch-worker.mjs +17 -0
- package/src/native/workspace-patch.mjs +17 -0
- package/src/native/workspace-paths.mjs +225 -0
- package/src/native/workspace-state.mjs +272 -0
- package/src/native/workspace-subject-observer.mjs +84 -0
- package/src/native/workspace-tools.mjs +1740 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native component_status tool port.
|
|
3
|
+
*
|
|
4
|
+
* Ports ksadk.toolsets.platform.component_status: a read-only tool that reports
|
|
5
|
+
* the AgentEngine built-in toolset and runtime binding status. The Host passes
|
|
6
|
+
* a fixed admission status snapshot at construction; the port never reads
|
|
7
|
+
* process.env, the filesystem, or the network, never auto-discovers bindings,
|
|
8
|
+
* and never exposes credentials. A component that is not bound is reported
|
|
9
|
+
* explicitly as not enabled.
|
|
10
|
+
*
|
|
11
|
+
* Status is data, not a capability authorization: the real Native ledger owns
|
|
12
|
+
* authority via the required assertCurrent callback, and status never lets the
|
|
13
|
+
* model choose an environment, service endpoint, or backend.
|
|
14
|
+
*/
|
|
15
|
+
import { canonical, copy, invariant, scopeKey, toolInputRejection, matchesToolExecutionContract } from '@kingsoftcloud/agentkit-harness-core';
|
|
16
|
+
|
|
17
|
+
export const NATIVE_COMPONENT_TOOLS_CONTRACT = 'agentkit.native-component-tools/v1';
|
|
18
|
+
|
|
19
|
+
const MAX_RESULT_BYTES = 64 * 1024;
|
|
20
|
+
const MAX_ARRAY = 4096;
|
|
21
|
+
const MAX_STRING = 8192;
|
|
22
|
+
const ENABLED_DISABLED = new Set(['enabled', 'not_enabled']);
|
|
23
|
+
|
|
24
|
+
/** Reject any key in object that is not in the allowlist. */
|
|
25
|
+
function rejectUnknownKeys(object, allow, prefix) {
|
|
26
|
+
for (const key of Object.keys(object)) {
|
|
27
|
+
invariant(allow.has(key), `component_status_unknown_field:${prefix}${key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Validate a Host-provided status snapshot with precise per-field allowlists
|
|
32
|
+
* at every nesting level. Unknown fields (including secrets) are rejected,
|
|
33
|
+
* never silently dropped. Returns a frozen deep clone the caller cannot
|
|
34
|
+
* mutate after construction. Missing tool lists default to empty — the port
|
|
35
|
+
* never claims tools are available that the Host did not bind. */
|
|
36
|
+
export function componentStatusSnapshot(raw) {
|
|
37
|
+
invariant(raw && typeof raw === 'object' && !Array.isArray(raw), 'component_status_invalid');
|
|
38
|
+
rejectUnknownKeys(raw, new Set(['summary', 'skill_space', 'skill_runtime', 'sandbox', 'workspace']), '');
|
|
39
|
+
|
|
40
|
+
const summary = group(raw.summary, new Set(['model', 'knowledge_base_bound', 'long_term_memory_bound',
|
|
41
|
+
'skill_space_bound', 'isolated_execution', 'sandbox_direct_tools']), 'summary.');
|
|
42
|
+
const skillSpace = group(raw.skill_space, new Set(['space_ids', 'user_space_ids', 'public_space_ids',
|
|
43
|
+
'service_url', 'aicp_endpoint_mode', 'tools']), 'skill_space.');
|
|
44
|
+
const skillRuntime = group(raw.skill_runtime, new Set(['backend', 'enabled', 'template_bound',
|
|
45
|
+
'request_protocol']), 'skill_runtime.');
|
|
46
|
+
const sandbox = group(raw.sandbox, new Set(['backend', 'enabled', 'template_bound', 'tools']), 'sandbox.');
|
|
47
|
+
const workspace = group(raw.workspace, new Set(['root', 'tools', 'boundary', 'editing_model']), 'workspace.');
|
|
48
|
+
|
|
49
|
+
const result = deepFreeze(copy({
|
|
50
|
+
// ok:true is a Host-fixed read-only result the port always emits (mirrors
|
|
51
|
+
// the Python component_status return). It is NOT accepted from the input
|
|
52
|
+
// status snapshot — the model cannot set it.
|
|
53
|
+
ok: true,
|
|
54
|
+
summary: {
|
|
55
|
+
model: stringField(summary.model),
|
|
56
|
+
knowledge_base_bound: boolField(summary.knowledge_base_bound),
|
|
57
|
+
long_term_memory_bound: boolField(summary.long_term_memory_bound),
|
|
58
|
+
skill_space_bound: boolField(summary.skill_space_bound),
|
|
59
|
+
isolated_execution: enabledDisabled(summary.isolated_execution),
|
|
60
|
+
sandbox_direct_tools: enabledDisabled(summary.sandbox_direct_tools),
|
|
61
|
+
},
|
|
62
|
+
skill_space: {
|
|
63
|
+
space_ids: stringArrayField(skillSpace.space_ids),
|
|
64
|
+
user_space_ids: stringArrayField(skillSpace.user_space_ids),
|
|
65
|
+
public_space_ids: stringArrayField(skillSpace.public_space_ids),
|
|
66
|
+
service_url: stringField(skillSpace.service_url),
|
|
67
|
+
aicp_endpoint_mode: stringField(skillSpace.aicp_endpoint_mode) || 'auto',
|
|
68
|
+
tools: stringArrayField(skillSpace.tools),
|
|
69
|
+
},
|
|
70
|
+
skill_runtime: {
|
|
71
|
+
backend: stringField(skillRuntime.backend) || 'disabled',
|
|
72
|
+
enabled: boolField(skillRuntime.enabled),
|
|
73
|
+
template_bound: boolField(skillRuntime.template_bound),
|
|
74
|
+
request_protocol: stringField(skillRuntime.request_protocol) || '--request-file JSON envelope',
|
|
75
|
+
},
|
|
76
|
+
sandbox: {
|
|
77
|
+
backend: stringField(sandbox.backend) || 'disabled',
|
|
78
|
+
enabled: boolField(sandbox.enabled),
|
|
79
|
+
template_bound: boolField(sandbox.template_bound),
|
|
80
|
+
tools: stringArrayField(sandbox.tools),
|
|
81
|
+
},
|
|
82
|
+
workspace: {
|
|
83
|
+
root: stringField(workspace.root),
|
|
84
|
+
tools: stringArrayField(workspace.tools),
|
|
85
|
+
boundary: stringField(workspace.boundary)
|
|
86
|
+
|| 'Workspace tools are confined to the AgentEngine UI workspace directory.',
|
|
87
|
+
editing_model: stringField(workspace.editing_model)
|
|
88
|
+
|| 'write tools support whole-file writes; edit_workspace_file supports exact snippet '
|
|
89
|
+
+ 'replacement; lint_workspace_file provides lightweight built-in checks.',
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
invariant(Buffer.byteLength(canonical(result), 'utf-8') <= MAX_RESULT_BYTES, 'component_status_oversized');
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function group(value, allow, prefix) {
|
|
97
|
+
if (value === undefined || value === null) return {};
|
|
98
|
+
invariant(value && typeof value === 'object' && !Array.isArray(value), 'component_status_field_invalid');
|
|
99
|
+
rejectUnknownKeys(value, allow, prefix);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function boolField(value) {
|
|
104
|
+
invariant(value === undefined || typeof value === 'boolean', 'component_status_field_invalid');
|
|
105
|
+
return value === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function stringField(value) {
|
|
109
|
+
if (value === undefined || value === null) return '';
|
|
110
|
+
invariant(typeof value === 'string' && value.length <= MAX_STRING, 'component_status_field_invalid');
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stringArrayField(value) {
|
|
115
|
+
if (value === undefined || value === null) return [];
|
|
116
|
+
invariant(Array.isArray(value) && value.length <= MAX_ARRAY, 'component_status_field_invalid');
|
|
117
|
+
for (const item of value) invariant(typeof item === 'string' && item.length <= MAX_STRING, 'component_status_field_invalid');
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function enabledDisabled(value) {
|
|
122
|
+
if (value === undefined || value === null) return 'not_enabled';
|
|
123
|
+
if (typeof value === 'boolean') return value ? 'enabled' : 'not_enabled';
|
|
124
|
+
invariant(typeof value === 'string' && ENABLED_DISABLED.has(value), 'component_status_field_invalid');
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function deepFreeze(value) {
|
|
129
|
+
if (value && typeof value === 'object') {
|
|
130
|
+
Object.freeze(value);
|
|
131
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function errorResult(code) {
|
|
137
|
+
return { status: 'error', content: { code, executed: false } };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const FORMAL_DEFINITION = deepFreeze({
|
|
141
|
+
name: 'component_status',
|
|
142
|
+
description: 'Report the AgentEngine built-in toolset and runtime binding status '
|
|
143
|
+
+ '(model, knowledge base, long-term memory, skill space, skill runtime, sandbox, workspace). '
|
|
144
|
+
+ 'Read-only: returns the Host-fixed admission snapshot; does not discover bindings, read '
|
|
145
|
+
+ 'environment variables, or expose credentials.',
|
|
146
|
+
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
|
147
|
+
readOnly: true,
|
|
148
|
+
concurrentSafe: true,
|
|
149
|
+
permissions: [],
|
|
150
|
+
timeoutMs: 30000,
|
|
151
|
+
contextRole: 'none',
|
|
152
|
+
observationRole: 'none',
|
|
153
|
+
progressObservation: false,
|
|
154
|
+
contextRetention: 'normal',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/** Return an isolated copy of the fixed formal component_status definition.
|
|
158
|
+
* The Host uses this to declare the tool schema before a real Session instance
|
|
159
|
+
* exists. It does not construct a fake execution identity — only the
|
|
160
|
+
* definition shape. Each call returns a fresh copy so a caller mutating the
|
|
161
|
+
* returned array or definition cannot affect the port's own definitions. */
|
|
162
|
+
export function nativeComponentToolDefinitions() {
|
|
163
|
+
return [copy(FORMAL_DEFINITION)];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {object} opts
|
|
168
|
+
* @param {object} opts.scope Trusted bound RunScope (validated via scopeKey).
|
|
169
|
+
* @param {string} opts.sessionId Trusted bound session id.
|
|
170
|
+
* @param {object} opts.status Host-fixed admission status snapshot.
|
|
171
|
+
* @param {(request:object)=>boolean} opts.assertCurrent Host authority check (required).
|
|
172
|
+
* @returns {{contract,definitions,execute(request):Promise<object>,close()}}
|
|
173
|
+
*/
|
|
174
|
+
export function createNativeComponentTools({ scope, sessionId, status, assertCurrent } = {}) {
|
|
175
|
+
invariant(scope && typeof scope === 'object', 'component_status_scope_missing');
|
|
176
|
+
// Validate all four scope domains via the core scopeKey (throws on missing/empty).
|
|
177
|
+
scopeKey(scope);
|
|
178
|
+
invariant(typeof sessionId === 'string' && sessionId.trim(), 'component_status_session_id_missing');
|
|
179
|
+
invariant(typeof assertCurrent === 'function', 'component_status_assert_current_required');
|
|
180
|
+
|
|
181
|
+
const boundScope = deepFreeze(copy(scope));
|
|
182
|
+
const boundSessionId = String(sessionId);
|
|
183
|
+
const frozenStatus = componentStatusSnapshot(status);
|
|
184
|
+
const statusCanonical = canonical(frozenStatus);
|
|
185
|
+
invariant(Buffer.byteLength(statusCanonical, 'utf-8') <= MAX_RESULT_BYTES, 'component_status_oversized');
|
|
186
|
+
|
|
187
|
+
const frozenDefinition = deepFreeze(copy(FORMAL_DEFINITION));
|
|
188
|
+
const definitions = Object.freeze([frozenDefinition]);
|
|
189
|
+
|
|
190
|
+
let closed = false;
|
|
191
|
+
|
|
192
|
+
/** Strict synchronous authority + identity guard. Runs before AND after the
|
|
193
|
+
* assertCurrent call. Verifies: closed, original signal instance ===, signal
|
|
194
|
+
* not aborted, bound scope/session match, and the sealed request body has not
|
|
195
|
+
* drifted. assertCurrent must return a strict boolean; a thenable is a
|
|
196
|
+
* contract violation whose rejection is observed (never an
|
|
197
|
+
* unhandledRejection). A getter/throw is converted to a bounded error. */
|
|
198
|
+
function guard(sealed, request) {
|
|
199
|
+
if (closed) return { ok: false, code: 'component_status_closed' };
|
|
200
|
+
if (request.signal !== sealed.originalSignal) return { ok: false, code: 'component_status_signal_drifted' };
|
|
201
|
+
if (sealed.originalSignal.aborted) return { ok: false, code: 'component_status_canceled' };
|
|
202
|
+
if (canonical(request.scope) !== sealed.scopeCanonical) return { ok: false, code: 'component_status_scope_drifted' };
|
|
203
|
+
if (request.sessionId !== boundSessionId) return { ok: false, code: 'component_status_session_drifted' };
|
|
204
|
+
let live;
|
|
205
|
+
try { live = canonical(sealed.stripSignal(request)); }
|
|
206
|
+
catch { return { ok: false, code: 'component_status_request_invalid' }; }
|
|
207
|
+
if (live !== sealed.bodyCanonical) return { ok: false, code: 'component_status_body_drifted' };
|
|
208
|
+
return { ok: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function callAssertCurrent(request) {
|
|
212
|
+
let verdict;
|
|
213
|
+
try { verdict = assertCurrent(request); }
|
|
214
|
+
catch { return { ok: false, code: 'component_status_assert_current_invalid' }; }
|
|
215
|
+
if (verdict && typeof verdict === 'object' && typeof verdict.then === 'function') {
|
|
216
|
+
verdict.then(() => {}, () => {}); // observe the rejection
|
|
217
|
+
return { ok: false, code: 'component_status_assert_current_invalid' };
|
|
218
|
+
}
|
|
219
|
+
if (typeof verdict !== 'boolean') return { ok: false, code: 'component_status_assert_current_invalid' };
|
|
220
|
+
if (!verdict) return { ok: false, code: 'component_status_authority_lost' };
|
|
221
|
+
return { ok: true };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function execute(request) {
|
|
225
|
+
if (!request || typeof request !== 'object') return errorResult('component_status_request_invalid');
|
|
226
|
+
|
|
227
|
+
// Validate formal parameters before anything else.
|
|
228
|
+
const rejection = toolInputRejection(frozenDefinition, request.call);
|
|
229
|
+
if (rejection) return { status: 'error', content: rejection };
|
|
230
|
+
|
|
231
|
+
// Compare the fixed execution protocol; the full original definition is
|
|
232
|
+
// still sealed into, and checked by, the current intent authority.
|
|
233
|
+
if (!matchesToolExecutionContract(request.definition, frozenDefinition))
|
|
234
|
+
return errorResult('component_status_definition_mismatch');
|
|
235
|
+
|
|
236
|
+
// Require a real AbortSignal by instance identity.
|
|
237
|
+
const signal = request.signal;
|
|
238
|
+
if (!(signal instanceof AbortSignal)) return errorResult('component_status_signal_missing');
|
|
239
|
+
|
|
240
|
+
// Validate the full Native identity fields are present and match the bound
|
|
241
|
+
// scope/session. The model cannot call with a foreign principal.
|
|
242
|
+
if (canonical(request.scope) !== canonical(boundScope)) return errorResult('component_status_scope_mismatch');
|
|
243
|
+
if (request.sessionId !== boundSessionId) return errorResult('component_status_session_mismatch');
|
|
244
|
+
|
|
245
|
+
// Seal the complete request body (excluding the non-JSON signal) and the
|
|
246
|
+
// original signal instance so every guard can detect drift.
|
|
247
|
+
const stripSignal = ({ signal: _s, ...rest }) => rest;
|
|
248
|
+
let bodyCanonical;
|
|
249
|
+
try { bodyCanonical = canonical(stripSignal(request)); }
|
|
250
|
+
catch { return errorResult('component_status_request_invalid'); }
|
|
251
|
+
const sealed = { originalSignal: signal, scopeCanonical: canonical(boundScope), bodyCanonical, stripSignal };
|
|
252
|
+
|
|
253
|
+
// Guard before authority.
|
|
254
|
+
let pre = guard(sealed, request);
|
|
255
|
+
if (!pre.ok) return errorResult(pre.code);
|
|
256
|
+
|
|
257
|
+
// Authority check.
|
|
258
|
+
let auth = callAssertCurrent(request);
|
|
259
|
+
if (!auth.ok) return errorResult(auth.code);
|
|
260
|
+
|
|
261
|
+
// Guard after authority: closed, signal, body, scope must not have drifted.
|
|
262
|
+
let post = guard(sealed, request);
|
|
263
|
+
if (!post.ok) return errorResult(post.code);
|
|
264
|
+
|
|
265
|
+
// Build a fresh copy of the frozen snapshot for this call.
|
|
266
|
+
const payload = copy(frozenStatus);
|
|
267
|
+
|
|
268
|
+
// Final guard: re-verify closed/cancel/body/signal one last time before
|
|
269
|
+
// returning. A close or abort that lands between the post-guard and here
|
|
270
|
+
// is caught.
|
|
271
|
+
let final = guard(sealed, request);
|
|
272
|
+
if (!final.ok) return errorResult(final.code);
|
|
273
|
+
|
|
274
|
+
return { status: 'ok', content: payload };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function close() { closed = true; }
|
|
278
|
+
|
|
279
|
+
return { contract: NATIVE_COMPONENT_TOOLS_CONTRACT, definitions, execute, close };
|
|
280
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonical, invariant } from '@kingsoftcloud/agentkit-harness-core';
|
|
3
|
+
import { buildNativeModelRequest } from './model-request.mjs';
|
|
4
|
+
|
|
5
|
+
// Pinned reference: native_planning.py + workspace_read_budget.py at 355059d.
|
|
6
|
+
// These are conservative estimated tokens, never reported provider usage or a
|
|
7
|
+
// claim of exact provider tokenization. The wire projection is the HTTP one.
|
|
8
|
+
export const NATIVE_CONTEXT_COUNTER_ID = 'agentkit.studio-cjk-ascii-estimate-125-wire-image/v1';
|
|
9
|
+
const whitespace = /^[\u0009-\u000d\u001c-\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u001c-\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/gu;
|
|
10
|
+
export function estimateNativeText(text) {
|
|
11
|
+
invariant(typeof text === 'string', 'context_counter_text_invalid');
|
|
12
|
+
let cjk = 0, other = 0;
|
|
13
|
+
for (const char of text.replace(whitespace, '')) {
|
|
14
|
+
const code = char.codePointAt(0);
|
|
15
|
+
if (code >= 0x3400 && code <= 0x4dbf || code >= 0x4e00 && code <= 0x9fff || code >= 0xf900 && code <= 0xfaff) cjk++;
|
|
16
|
+
else other++;
|
|
17
|
+
}
|
|
18
|
+
return Math.max(1, Math.ceil((Math.floor(cjk * 1.5) + Math.ceil(other / 4)) * 1.25));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Estimate actual provider fields, charging image bytes separately from text. */
|
|
22
|
+
export function estimateNativeWireInput(body, protocol = Array.isArray(body.input) && !body.messages ? 'openai-responses' : 'openai-chat') {
|
|
23
|
+
const wire = structuredClone(body);
|
|
24
|
+
for (const key of ['model', 'stream', 'stream_options', 'max_tokens', 'max_completion_tokens', 'max_output_tokens']) delete wire[key];
|
|
25
|
+
if (protocol === 'anthropic-messages') {
|
|
26
|
+
expandToolReferences(wire.messages, wire.tools);
|
|
27
|
+
// Deferred definitions are HTTP catalog data, not initial model context.
|
|
28
|
+
if (wire.tools) wire.tools = wire.tools.filter(tool => tool.defer_loading !== true);
|
|
29
|
+
}
|
|
30
|
+
return priceWireSurface(wire, protocol === 'openai-responses' ? wire.input : wire.messages);
|
|
31
|
+
}
|
|
32
|
+
function expandToolReferences(messages = [], tools = []) {
|
|
33
|
+
const catalog = new Map(tools.map(tool => [tool.name, tool]));
|
|
34
|
+
for (const message of messages) {
|
|
35
|
+
if (!Array.isArray(message.content)) continue;
|
|
36
|
+
for (const result of message.content) {
|
|
37
|
+
if (result.type !== 'tool_result' || !Array.isArray(result.content)) continue;
|
|
38
|
+
result.content = result.content.map(block => {
|
|
39
|
+
if (block.type !== 'tool_reference') return block;
|
|
40
|
+
const definition = catalog.get(block.tool_name);
|
|
41
|
+
invariant(definition, 'context_counter_tool_reference_unknown');
|
|
42
|
+
const { defer_loading: _deferred, cache_control: _cache, ...expanded } = definition;
|
|
43
|
+
return { type: 'text', text: canonical(expanded) };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function priceWireSurface(surface, messages = []) {
|
|
49
|
+
let images = 0;
|
|
50
|
+
const reserve = url => {
|
|
51
|
+
invariant(typeof url === 'string' && url, 'context_counter_image_invalid');
|
|
52
|
+
images += url.startsWith('data:image/') ? Math.max(2048, Math.ceil(url.slice(url.indexOf(',') + 1).length / 8)) : 8192;
|
|
53
|
+
};
|
|
54
|
+
const visit = value => {
|
|
55
|
+
if (value === null || typeof value !== 'object') return;
|
|
56
|
+
if (value.type === 'image_url' && value.image_url?.url) {
|
|
57
|
+
reserve(value.image_url.url); value.image_url = { url: '[Host image budgeted separately]' }; return;
|
|
58
|
+
}
|
|
59
|
+
if (value.type === 'input_image' && typeof value.image_url === 'string') {
|
|
60
|
+
reserve(value.image_url); value.image_url = '[Host image budgeted separately]'; return;
|
|
61
|
+
}
|
|
62
|
+
if (value.type === 'image' && value.source) {
|
|
63
|
+
const source = value.source;
|
|
64
|
+
if (source.type === 'base64') reserve(`data:${source.media_type};base64,${source.data}`);
|
|
65
|
+
else if (source.type === 'url') reserve(source.url);
|
|
66
|
+
else invariant(false, 'context_counter_image_invalid');
|
|
67
|
+
value.source = { type: 'host-budgeted-image' }; return;
|
|
68
|
+
}
|
|
69
|
+
if (value.type === 'tool_result' && Array.isArray(value.content)) value.content.forEach(visit);
|
|
70
|
+
};
|
|
71
|
+
// Only provider message slots contain image blocks. Tool schemas and text
|
|
72
|
+
// may describe image-shaped data; that data must be priced as ordinary JSON.
|
|
73
|
+
for (const message of messages ?? []) {
|
|
74
|
+
if (Array.isArray(message.content)) message.content.forEach(visit);
|
|
75
|
+
if (message.type === 'function_call_output' && Array.isArray(message.output)) message.output.forEach(visit);
|
|
76
|
+
}
|
|
77
|
+
const count = estimateNativeText(canonical(surface)) + images;
|
|
78
|
+
invariant(Number.isSafeInteger(count) && count >= 0, 'context_counter_overflow');
|
|
79
|
+
return count;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Price actual provider fields for one logical message, including replay/media.
|
|
83
|
+
* Synthetic protocol partners only make the normal wire builder callable; their
|
|
84
|
+
* fields are excluded from the returned cost. No model or tool is executed. */
|
|
85
|
+
export function estimateNativeMessage(binding, request) {
|
|
86
|
+
const message = structuredClone(request.message), messages = [message];
|
|
87
|
+
if (message.role === 'assistant' && message.toolCalls?.length) {
|
|
88
|
+
messages.push(...message.toolCalls.map(call => ({ role: 'tool', name: call.name, callId: call.callId, content: '' })));
|
|
89
|
+
} else if (message.role === 'tool') {
|
|
90
|
+
messages.unshift({ role: 'assistant', content: '', toolCalls: [{ callId: message.callId,
|
|
91
|
+
name: request.profile.providerToolSearchPolicy === 'anthropic-deferred/v1' && message.discoveredToolNames !== undefined
|
|
92
|
+
? 'ToolSearch' : message.name || 'source', arguments: {} }] });
|
|
93
|
+
}
|
|
94
|
+
const tools = request.profile.providerToolSearchPolicy === 'anthropic-deferred/v1' ? request.tools ?? [] : [];
|
|
95
|
+
const built = buildNativeModelRequest(binding, { ...request, messages, tools, finalizationOnly: false });
|
|
96
|
+
const body = JSON.parse(built.body);
|
|
97
|
+
let projected;
|
|
98
|
+
if (binding.protocol === 'openai-responses') {
|
|
99
|
+
if (message.role === 'system') return priceWireSurface({ instructions: body.instructions ?? '' });
|
|
100
|
+
projected = body.input.filter(item => message.role === 'tool' ? item.type !== 'function_call'
|
|
101
|
+
: message.role === 'assistant' ? item.type !== 'function_call_output' : true);
|
|
102
|
+
} else if (binding.protocol === 'anthropic-messages' && message.role === 'system') {
|
|
103
|
+
return priceWireSurface(body.system ?? '');
|
|
104
|
+
} else {
|
|
105
|
+
projected = body.messages.filter(item => message.role === 'tool' ? item.role !== 'assistant'
|
|
106
|
+
: message.role === 'assistant' ? item.role === 'assistant' : true);
|
|
107
|
+
}
|
|
108
|
+
invariant(projected.length > 0, 'context_counter_message_invalid');
|
|
109
|
+
if (binding.protocol === 'anthropic-messages') expandToolReferences(projected, body.tools);
|
|
110
|
+
return priceWireSurface(projected.length === 1 ? projected[0] : projected, projected);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function createNativeContextPort({ protocol, model, parameters = {} } = {}) {
|
|
114
|
+
invariant(['openai-chat', 'openai-responses', 'anthropic-messages'].includes(protocol)
|
|
115
|
+
&& typeof model === 'string' && model.trim(), 'context_counter_binding_invalid');
|
|
116
|
+
const binding = { protocol, model, parameters: structuredClone(parameters),
|
|
117
|
+
baseUrl: 'https://native-counter.invalid/v1', apiKey: 'counting-only' };
|
|
118
|
+
const tokenizerDigest = createHash('sha256').update(canonical({ policy: NATIVE_CONTEXT_COUNTER_ID,
|
|
119
|
+
protocol, model, parameters: binding.parameters })).digest('hex');
|
|
120
|
+
return { tokens: Object.freeze({ tokenizerId: NATIVE_CONTEXT_COUNTER_ID, tokenizerDigest,
|
|
121
|
+
async countText(request) { request.signal.throwIfAborted(); return estimateNativeText(request.text); },
|
|
122
|
+
async countMessage(request) {
|
|
123
|
+
request.signal.throwIfAborted(); const count = estimateNativeMessage(binding, request);
|
|
124
|
+
request.signal.throwIfAborted(); return count;
|
|
125
|
+
},
|
|
126
|
+
async countRequest(request) {
|
|
127
|
+
request.signal.throwIfAborted();
|
|
128
|
+
const built = buildNativeModelRequest(binding, { ...request, finalizationOnly: false });
|
|
129
|
+
const count = estimateNativeWireInput(JSON.parse(built.body), binding.protocol);
|
|
130
|
+
request.signal.throwIfAborted(); return count;
|
|
131
|
+
},
|
|
132
|
+
}) };
|
|
133
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { nativeDeliveryId } from './delivery-timeline.mjs';
|
|
2
|
+
import { copy } from '@kingsoftcloud/agentkit-harness-core';
|
|
3
|
+
|
|
4
|
+
/** Read-only timeline facts; proposals and summary model calls never mean adoption. */
|
|
5
|
+
export function advanceNativeContextMarkerState(previous, record) {
|
|
6
|
+
const { snapshot, events = [] } = record;
|
|
7
|
+
const state = copy(previous ?? { anchor: { kind: 'start' }, adopted: 0, markers: [] });
|
|
8
|
+
let { anchor, adopted } = state;
|
|
9
|
+
const markers = state.markers;
|
|
10
|
+
let active = markers.findLast(fact => fact.settledSeq === null && fact.marker.status === 'running') ?? null;
|
|
11
|
+
for (const event of events) {
|
|
12
|
+
if (event.kind === 'model.completed' && event.eventId) anchor = {
|
|
13
|
+
kind: 'message', id: nativeDeliveryId(snapshot.scope, snapshot.runId, `response:${event.eventId}`),
|
|
14
|
+
};
|
|
15
|
+
if (event.kind === 'tool.completed' && event.payload.callId) anchor = {
|
|
16
|
+
kind: 'tool', id: nativeDeliveryId(snapshot.scope, snapshot.runId, `call:${event.payload.callId}`),
|
|
17
|
+
};
|
|
18
|
+
if (event.kind !== 'context.planned') continue;
|
|
19
|
+
const count = event.payload.compactions;
|
|
20
|
+
if (Number.isSafeInteger(count) && count > adopted) {
|
|
21
|
+
if (active) { active.marker.status = 'completed'; active.settledSeq = event.seq ?? null; active = null; }
|
|
22
|
+
// Never invent a missing historical start or position.
|
|
23
|
+
adopted = count;
|
|
24
|
+
}
|
|
25
|
+
if (event.payload.action === 'blocked' && active) { active.marker.status = 'failed'; active.settledSeq = event.seq ?? null; active = null; }
|
|
26
|
+
if (event.payload.action === 'compact' && !active && event.eventId) {
|
|
27
|
+
active = { marker: { id: nativeDeliveryId(snapshot.scope, snapshot.runId, `context:${event.eventId}`),
|
|
28
|
+
status: 'running', origin: 'auto', after: { ...anchor } }, settledSeq: null };
|
|
29
|
+
markers.push(active);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { anchor, adopted, markers };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function nativeContextMarkerFactsFromState(state, snapshot, { driving = false } = {}) {
|
|
36
|
+
const markers = copy(state.markers);
|
|
37
|
+
const active = markers.findLast(fact => fact.settledSeq === null && fact.marker.status === 'running');
|
|
38
|
+
if (active && !driving) {
|
|
39
|
+
active.marker.status = snapshot.phase === 'failed' ? 'failed' : 'interrupted';
|
|
40
|
+
if (['completed', 'failed', 'canceled', 'interrupted'].includes(snapshot.phase)) active.settledSeq = snapshot.eventSeq;
|
|
41
|
+
}
|
|
42
|
+
return markers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function nativeContextMarkerFacts(record, options) {
|
|
46
|
+
return nativeContextMarkerFactsFromState(advanceNativeContextMarkerState(null, record), record.snapshot, options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function nativeContextMarkers(record, options) {
|
|
50
|
+
return nativeContextMarkerFacts(record, options).map(fact => fact.marker);
|
|
51
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { invariant } from '@kingsoftcloud/agentkit-harness-core';
|
|
2
|
+
import { createNativeContextPort } from '@kingsoftcloud/agentkit-harness-node/native/context-counter';
|
|
3
|
+
|
|
4
|
+
/** Restore the admitted context strategies rather than activating new defaults on an old Run. */
|
|
5
|
+
export function nativeContextDefaults(configuration) {
|
|
6
|
+
const policy = configuration?.contextPolicy;
|
|
7
|
+
return {
|
|
8
|
+
structuredContinuityDefaults: !configuration || policy?.summaryFormatPolicy === 'task-continuity/v1',
|
|
9
|
+
readWorkingSetDefaults: !configuration || Object.hasOwn(policy ?? {}, 'readWorkingSetCount'),
|
|
10
|
+
recentToolBatchDefaults: !configuration || Object.hasOwn(policy ?? {}, 'recentToolBatchCount'),
|
|
11
|
+
pruningThresholdDefaults: !configuration || policy?.pruneThresholdPolicy === 'zcode-local/v1',
|
|
12
|
+
pruningSummaryDefaults: !configuration || policy?.pruneSummaryPolicy === 'zcode-local/v1',
|
|
13
|
+
pruningIdleDefaults: !configuration || policy?.pruneIdleThresholdMinutes !== undefined,
|
|
14
|
+
preModelBudgetDefaults: !configuration || policy?.budgetTiming === 'before-model/v1',
|
|
15
|
+
orderedContextDefaults: !configuration || policy?.continuityPlacement === 'after-history/v1',
|
|
16
|
+
sourceReuseDefaults: !configuration || policy?.sourceReusePolicy === 'captured-source/v1',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Bind budget, counter and policy from the resolved Agent revision/Frozen Build. */
|
|
21
|
+
export function bindNativeContext({ description, context = {}, maxTokens, structuredContinuityDefaults = false, preModelBudgetDefaults = false, orderedContextDefaults = false, sourceReuseDefaults = false, readWorkingSetDefaults = false, recentToolBatchDefaults = false, pruningThresholdDefaults = false, pruningSummaryDefaults = false, pruningIdleDefaults = false } = {}) {
|
|
22
|
+
invariant(context && typeof context === 'object' && !Array.isArray(context), 'studio_native_context_invalid');
|
|
23
|
+
const raw = context.compaction ?? {};
|
|
24
|
+
invariant(raw && typeof raw === 'object' && !Array.isArray(raw), 'studio_native_compaction_invalid');
|
|
25
|
+
const supported = ['enabled', 'thresholdRatio', 'targetRatio', 'summaryMaxTokens', 'maxCompactions',
|
|
26
|
+
'maxCompactionsWithoutProgress', 'minModelTurnsBetween', 'maxSummaryRetries',
|
|
27
|
+
'readWorkingSetCount', 'readWorkingSetMaxTokens', 'readWorkingSetTotalMaxTokens',
|
|
28
|
+
'recentToolBatchCount', 'recentToolBatchMaxTokens', 'recentToolBatchesTotalMaxTokens',
|
|
29
|
+
'pruneToolResults', 'pruneKeepRecentBatches', 'pruneMinSavedTokens', 'pruneThresholdTokens', 'pruneIdleThresholdMinutes'];
|
|
30
|
+
invariant(Object.keys(raw).every(key => supported.includes(key)), 'studio_native_compaction_option_unsupported');
|
|
31
|
+
const requestedWindow = context.maxInputTokens ?? description.contextWindow;
|
|
32
|
+
const output = maxTokens ?? context.reserveOutputTokens ?? description.maxOutputTokens;
|
|
33
|
+
invariant(Number.isSafeInteger(requestedWindow) && requestedWindow > 0
|
|
34
|
+
&& Number.isSafeInteger(output) && output > 0 && output <= description.maxOutputTokens, 'studio_native_context_budget_invalid');
|
|
35
|
+
const contextWindow = Math.min(requestedWindow, description.contextWindow);
|
|
36
|
+
invariant(output < contextWindow, 'studio_native_context_budget_conflict');
|
|
37
|
+
const port = createNativeContextPort({ protocol: description.protocol, model: description.modelName, parameters: description.parameters });
|
|
38
|
+
const triggerRatio = raw.thresholdRatio ?? .72;
|
|
39
|
+
return { port, profile: { contextWindow, maxOutputTokens: output }, policy: {
|
|
40
|
+
tokenizerId: port.tokens.tokenizerId, tokenizerDigest: port.tokens.tokenizerDigest,
|
|
41
|
+
summaryBudgetMode: 'context_bounded_estimate',
|
|
42
|
+
...(structuredContinuityDefaults ? { summaryFormatPolicy: 'task-continuity/v1' } : {}),
|
|
43
|
+
...(preModelBudgetDefaults ? { budgetTiming: 'before-model/v1' } : {}),
|
|
44
|
+
...(orderedContextDefaults && description.protocol === 'openai-chat' ? { continuityPlacement: 'after-history/v1' } : {}),
|
|
45
|
+
...(sourceReuseDefaults && description.protocol === 'openai-chat' ? { sourceReusePolicy: 'captured-source/v1' } : {}),
|
|
46
|
+
enabled: raw.enabled ?? true, triggerRatio, targetRatio: raw.targetRatio ?? Math.min(.45, triggerRatio * .45 / .72),
|
|
47
|
+
summaryMaxTokens: raw.summaryMaxTokens ?? Math.min(8192, output, Math.max(2048, Math.floor(contextWindow / 8))),
|
|
48
|
+
maxCompactions: raw.maxCompactions ?? null, maxCompactionsWithoutProgress: raw.maxCompactionsWithoutProgress ?? 8,
|
|
49
|
+
minModelTurnsBetween: raw.minModelTurnsBetween ?? 2, maxSummaryRetries: raw.maxSummaryRetries ?? 2,
|
|
50
|
+
...(['pruneToolResults', 'pruneKeepRecentBatches', 'pruneMinSavedTokens', 'pruneThresholdTokens', 'pruneIdleThresholdMinutes'].some(key => Object.hasOwn(raw, key)) ? {
|
|
51
|
+
pruneToolResults: raw.pruneToolResults ?? false, pruneKeepRecentBatches: raw.pruneKeepRecentBatches ?? 5,
|
|
52
|
+
pruneMinSavedTokens: raw.pruneMinSavedTokens ?? 256,
|
|
53
|
+
...(Object.hasOwn(raw, 'pruneThresholdTokens') ? { pruneThresholdTokens: raw.pruneThresholdTokens } : {}),
|
|
54
|
+
...(pruningThresholdDefaults && raw.pruneToolResults === true && !Object.hasOwn(raw, 'pruneThresholdTokens')
|
|
55
|
+
? { pruneThresholdPolicy: 'zcode-local/v1' } : {}),
|
|
56
|
+
...(pruningSummaryDefaults && raw.pruneToolResults === true ? { pruneSummaryPolicy: 'zcode-local/v1' } : {}),
|
|
57
|
+
...(Object.hasOwn(raw, 'pruneIdleThresholdMinutes') ? { pruneIdleThresholdMinutes: raw.pruneIdleThresholdMinutes }
|
|
58
|
+
: pruningIdleDefaults && raw.pruneToolResults === true ? { pruneIdleThresholdMinutes: 60 } : {}),
|
|
59
|
+
} : {}),
|
|
60
|
+
...(readWorkingSetDefaults || ['readWorkingSetCount', 'readWorkingSetMaxTokens', 'readWorkingSetTotalMaxTokens']
|
|
61
|
+
.some(key => Object.hasOwn(raw, key)) ? {
|
|
62
|
+
readWorkingSetCount: raw.readWorkingSetCount ?? 8, readWorkingSetMaxTokens: raw.readWorkingSetMaxTokens ?? 6000,
|
|
63
|
+
readWorkingSetTotalMaxTokens: raw.readWorkingSetTotalMaxTokens ?? 16000,
|
|
64
|
+
} : {}),
|
|
65
|
+
...(recentToolBatchDefaults || ['recentToolBatchCount', 'recentToolBatchMaxTokens', 'recentToolBatchesTotalMaxTokens']
|
|
66
|
+
.some(key => Object.hasOwn(raw, key)) ? {
|
|
67
|
+
recentToolBatchCount: raw.recentToolBatchCount ?? 4,
|
|
68
|
+
recentToolBatchMaxTokens: raw.recentToolBatchMaxTokens ?? Math.min(12000, Math.floor(contextWindow / 6)),
|
|
69
|
+
recentToolBatchesTotalMaxTokens: raw.recentToolBatchesTotalMaxTokens ?? Math.min(24000, Math.floor(contextWindow / 4)),
|
|
70
|
+
} : {}),
|
|
71
|
+
} };
|
|
72
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { nativeContextMarkers } from './context-markers.mjs';
|
|
2
|
+
/** Bounded, content-free view of the committed context planner. Read-only. */
|
|
3
|
+
export function nativeContextStatus(record, { driving = false } = {}) {
|
|
4
|
+
if (!record?.configuration.contextPolicy) return null;
|
|
5
|
+
const { snapshot, configuration, events = [] } = record;
|
|
6
|
+
let admission = null, lastAction = null, admissionSeq = null;
|
|
7
|
+
for (const event of events) if (event.kind === 'context.planned') {
|
|
8
|
+
lastAction = event.payload.action;
|
|
9
|
+
if (event.payload.admission) { admission = event.payload.admission; admissionSeq = event.seq; }
|
|
10
|
+
}
|
|
11
|
+
const policy = configuration.contextPolicy;
|
|
12
|
+
const state = snapshot.contextState;
|
|
13
|
+
// A proposal/model response is not a committed, adopted compaction.
|
|
14
|
+
const count = Number.isSafeInteger(state?.compactions) ? state.compactions : 0;
|
|
15
|
+
const compacting = snapshot.phase === 'compacting' || Boolean(state?.compaction);
|
|
16
|
+
return {
|
|
17
|
+
contract: 'agentkit.native-context-status/v1', runId: snapshot.runId, snapshotRevision: snapshot.revision,
|
|
18
|
+
enabled: policy.enabled, thresholdRatio: policy.triggerRatio, targetRatio: policy.targetRatio,
|
|
19
|
+
contextWindow: configuration.profile.contextWindow,
|
|
20
|
+
reservedOutputTokens: configuration.profile.maxOutputTokens,
|
|
21
|
+
lastRequestInputTokens: admission?.countedInputTokens ?? null,
|
|
22
|
+
lastAdmissionSeq: admissionSeq,
|
|
23
|
+
compactions: count,
|
|
24
|
+
markers: nativeContextMarkers(record, { driving }),
|
|
25
|
+
state: lastAction === 'blocked' ? 'blocked' : compacting ? driving ? 'compacting' : 'interrupted' : 'idle',
|
|
26
|
+
};
|
|
27
|
+
}
|