@vellumai/assistant 0.8.11 → 0.8.12-staging.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/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +633 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/bundler/app-compiler.ts +8 -0
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +225 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/chat-completions-provider.ts +4 -2
- package/src/providers/openai/responses-provider.ts +7 -2
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +380 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/security/redact-json.ts +61 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
package/src/acp/agent-process.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { Readable, Writable } from "node:stream";
|
|
|
10
10
|
|
|
11
11
|
import type {
|
|
12
12
|
Agent,
|
|
13
|
+
AuthMethod,
|
|
14
|
+
AuthMethodEnvVar,
|
|
13
15
|
Client,
|
|
14
16
|
InitializeResponse,
|
|
15
17
|
NewSessionResponse,
|
|
@@ -22,6 +24,31 @@ import type { AcpAgentConfig } from "./types.js";
|
|
|
22
24
|
|
|
23
25
|
const log = getLogger("acp");
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* JSON-RPC error code agents use to signal that authentication is required
|
|
29
|
+
* (matches the SDK's RequestError.authRequired()).
|
|
30
|
+
*/
|
|
31
|
+
const AUTH_REQUIRED_CODE = -32000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detects the ACP auth-required error. Checks the `code` property rather than
|
|
35
|
+
* `instanceof acp.RequestError` so plain JSON-RPC error objects are also
|
|
36
|
+
* recognized.
|
|
37
|
+
*/
|
|
38
|
+
function isAuthRequiredError(err: unknown): boolean {
|
|
39
|
+
return (
|
|
40
|
+
typeof err === "object" &&
|
|
41
|
+
err !== null &&
|
|
42
|
+
(err as { code?: unknown }).code === AUTH_REQUIRED_CODE
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isEnvVarMethod(
|
|
47
|
+
method: AuthMethod,
|
|
48
|
+
): method is AuthMethodEnvVar & { type: "env_var" } {
|
|
49
|
+
return "type" in method && method.type === "env_var";
|
|
50
|
+
}
|
|
51
|
+
|
|
25
52
|
/**
|
|
26
53
|
* Factory function type for creating ACP client handlers.
|
|
27
54
|
* PR 5 will provide the real VellumAcpClientHandler implementation.
|
|
@@ -35,6 +62,12 @@ export class AcpAgentProcess {
|
|
|
35
62
|
private proc: ChildProcess | null = null;
|
|
36
63
|
private connection: acp.ClientSideConnection | null = null;
|
|
37
64
|
private initializeResponse: InitializeResponse | null = null;
|
|
65
|
+
/**
|
|
66
|
+
* Merged env captured at spawn() so auth satisfiability checks match the
|
|
67
|
+
* env the child process actually received, even if process.env changes
|
|
68
|
+
* afterwards.
|
|
69
|
+
*/
|
|
70
|
+
private spawnedEnv: NodeJS.ProcessEnv | null = null;
|
|
38
71
|
|
|
39
72
|
constructor(
|
|
40
73
|
public readonly agentId: string,
|
|
@@ -51,10 +84,11 @@ export class AcpAgentProcess {
|
|
|
51
84
|
"Spawning ACP agent process",
|
|
52
85
|
);
|
|
53
86
|
|
|
87
|
+
this.spawnedEnv = { ...process.env, ...this.config.env };
|
|
54
88
|
this.proc = spawn(this.config.command, this.config.args, {
|
|
55
89
|
cwd,
|
|
56
90
|
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
-
env:
|
|
91
|
+
env: this.spawnedEnv,
|
|
58
92
|
});
|
|
59
93
|
|
|
60
94
|
const stream = acp.ndJsonStream(
|
|
@@ -94,13 +128,11 @@ export class AcpAgentProcess {
|
|
|
94
128
|
* Initializes the ACP connection by negotiating protocol version and capabilities.
|
|
95
129
|
*/
|
|
96
130
|
async initialize(): Promise<InitializeResponse> {
|
|
97
|
-
|
|
98
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
99
|
-
}
|
|
131
|
+
const connection = this.requireConnection();
|
|
100
132
|
|
|
101
133
|
log.info({ agentId: this.agentId }, "Initializing ACP connection");
|
|
102
134
|
|
|
103
|
-
const response = await
|
|
135
|
+
const response = await connection.initialize({
|
|
104
136
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
105
137
|
clientInfo: { name: "vellum", version: "1.0.0" },
|
|
106
138
|
clientCapabilities: {
|
|
@@ -133,20 +165,119 @@ export class AcpAgentProcess {
|
|
|
133
165
|
}
|
|
134
166
|
|
|
135
167
|
/**
|
|
136
|
-
*
|
|
137
|
-
* Returns
|
|
168
|
+
* Authentication methods the agent advertised at initialize.
|
|
169
|
+
* Returns an empty array before initialize() resolves.
|
|
138
170
|
*/
|
|
139
|
-
|
|
171
|
+
private get authMethods(): AuthMethod[] {
|
|
172
|
+
return this.initializeResponse?.authMethods ?? [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Selects the first advertised env_var auth method whose required variables
|
|
177
|
+
* are all present (non-empty) in the env the agent process was spawned with.
|
|
178
|
+
*
|
|
179
|
+
* Terminal-type and agent-driven (untyped) methods are never selected:
|
|
180
|
+
* auto-triggering an interactive login would hang the headless daemon.
|
|
181
|
+
*/
|
|
182
|
+
private selectEnvVarAuthMethod(): AuthMethod | undefined {
|
|
183
|
+
const env = this.spawnedEnv;
|
|
184
|
+
if (!env) return undefined;
|
|
185
|
+
|
|
186
|
+
return this.authMethods.find((method) => {
|
|
187
|
+
if (!isEnvVarMethod(method)) return false;
|
|
188
|
+
|
|
189
|
+
// `vars` is required by the SDK type, but agent responses aren't
|
|
190
|
+
// runtime-validated — tolerate an out-of-spec agent omitting it so the
|
|
191
|
+
// caller gets the friendly auth error instead of a TypeError.
|
|
192
|
+
const requiredVars = (method.vars ?? []).filter((v) => !v.optional);
|
|
193
|
+
if (requiredVars.length === 0) return false;
|
|
194
|
+
|
|
195
|
+
return requiredVars.every((v) => {
|
|
196
|
+
const value = env[v.name];
|
|
197
|
+
return typeof value === "string" && value.length > 0;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns the live connection, throwing the standard not-spawned error if
|
|
204
|
+
* the agent was never spawned or its process has since exited.
|
|
205
|
+
*/
|
|
206
|
+
private requireConnection(): acp.ClientSideConnection {
|
|
140
207
|
if (!this.connection) {
|
|
141
208
|
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
142
209
|
}
|
|
210
|
+
return this.connection;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Runs an operation, and if the agent rejects with the ACP auth-required
|
|
215
|
+
* error, authenticates via a satisfiable env_var auth method and retries
|
|
216
|
+
* the operation exactly once.
|
|
217
|
+
*/
|
|
218
|
+
private async withAuthRetry<T>(op: () => Promise<T>): Promise<T> {
|
|
219
|
+
try {
|
|
220
|
+
return await op();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (!isAuthRequiredError(err)) throw err;
|
|
223
|
+
|
|
224
|
+
// The agent may have exited between the auth_required rejection and
|
|
225
|
+
// this retry path; fail with the standard not-spawned error.
|
|
226
|
+
const connection = this.requireConnection();
|
|
227
|
+
|
|
228
|
+
const method = this.selectEnvVarAuthMethod();
|
|
229
|
+
if (!method) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`ACP agent "${this.agentId}" requires authentication. ` +
|
|
232
|
+
`Advertised methods: ${this.describeAuthMethods()}. ` +
|
|
233
|
+
"Set the required env var under acp.agents.<id>.env in config.json, " +
|
|
234
|
+
"store it via 'assistant credentials set --service acp --field <field>', " +
|
|
235
|
+
"or complete the agent's own login flow in the workspace.",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
log.info(
|
|
240
|
+
{ agentId: this.agentId, methodId: method.id },
|
|
241
|
+
"ACP agent returned auth_required; authenticating with env_var method",
|
|
242
|
+
);
|
|
143
243
|
|
|
244
|
+
await connection.authenticate({ methodId: method.id });
|
|
245
|
+
return await op();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Renders the agent's advertised auth methods for error messages, e.g.
|
|
251
|
+
* `"Login with ChatGPT" (chatgpt), "Use OPENAI_API_KEY" (env var OPENAI_API_KEY)`.
|
|
252
|
+
*/
|
|
253
|
+
private describeAuthMethods(): string {
|
|
254
|
+
if (this.authMethods.length === 0) return "none";
|
|
255
|
+
|
|
256
|
+
return this.authMethods
|
|
257
|
+
.map((method) => {
|
|
258
|
+
// `vars ?? []`: tolerate out-of-spec agents omitting the field —
|
|
259
|
+
// this renders inside the friendly auth error, which must not
|
|
260
|
+
// itself throw a TypeError.
|
|
261
|
+
const varNames = isEnvVarMethod(method)
|
|
262
|
+
? (method.vars ?? []).map((v) => v.name).join(", ")
|
|
263
|
+
: "";
|
|
264
|
+
return varNames
|
|
265
|
+
? `"${method.name}" (env var ${varNames})`
|
|
266
|
+
: `"${method.name}" (${method.id})`;
|
|
267
|
+
})
|
|
268
|
+
.join(", ");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Creates a new ACP session in the specified working directory.
|
|
273
|
+
* Returns the session ID.
|
|
274
|
+
*/
|
|
275
|
+
async createSession(cwd: string): Promise<string> {
|
|
144
276
|
log.info({ agentId: this.agentId, cwd }, "Creating ACP session");
|
|
145
277
|
|
|
146
|
-
const result: NewSessionResponse = await this.
|
|
147
|
-
cwd,
|
|
148
|
-
|
|
149
|
-
});
|
|
278
|
+
const result: NewSessionResponse = await this.withAuthRetry(() =>
|
|
279
|
+
this.requireConnection().newSession({ cwd, mcpServers: [] }),
|
|
280
|
+
);
|
|
150
281
|
|
|
151
282
|
return result.sessionId;
|
|
152
283
|
}
|
|
@@ -160,13 +291,11 @@ export class AcpAgentProcess {
|
|
|
160
291
|
* VellumAcpClientHandler.beginReplaySuppression).
|
|
161
292
|
*/
|
|
162
293
|
async loadSession(sessionId: string, cwd: string): Promise<void> {
|
|
163
|
-
if (!this.connection) {
|
|
164
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
294
|
log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
|
|
168
295
|
|
|
169
|
-
await this.
|
|
296
|
+
await this.withAuthRetry(() =>
|
|
297
|
+
this.requireConnection().loadSession({ sessionId, cwd, mcpServers: [] }),
|
|
298
|
+
);
|
|
170
299
|
}
|
|
171
300
|
|
|
172
301
|
/**
|
|
@@ -177,13 +306,15 @@ export class AcpAgentProcess {
|
|
|
177
306
|
* (see supportsSessionResume).
|
|
178
307
|
*/
|
|
179
308
|
async resumeSession(sessionId: string, cwd: string): Promise<void> {
|
|
180
|
-
if (!this.connection) {
|
|
181
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
309
|
log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
|
|
185
310
|
|
|
186
|
-
await this.
|
|
311
|
+
await this.withAuthRetry(() =>
|
|
312
|
+
this.requireConnection().resumeSession({
|
|
313
|
+
sessionId,
|
|
314
|
+
cwd,
|
|
315
|
+
mcpServers: [],
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
187
318
|
}
|
|
188
319
|
|
|
189
320
|
/**
|
|
@@ -191,35 +322,31 @@ export class AcpAgentProcess {
|
|
|
191
322
|
* Returns the prompt response (includes stopReason).
|
|
192
323
|
*/
|
|
193
324
|
async prompt(sessionId: string, text: string): Promise<PromptResponse> {
|
|
194
|
-
if (!this.connection) {
|
|
195
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
325
|
log.info(
|
|
199
326
|
{ agentId: this.agentId, sessionId },
|
|
200
327
|
"Sending prompt to ACP agent",
|
|
201
328
|
);
|
|
202
329
|
|
|
203
|
-
return this.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
330
|
+
return this.withAuthRetry(() =>
|
|
331
|
+
this.requireConnection().prompt({
|
|
332
|
+
sessionId,
|
|
333
|
+
prompt: [{ type: "text", text }],
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
207
336
|
}
|
|
208
337
|
|
|
209
338
|
/**
|
|
210
339
|
* Cancels an ongoing prompt in the specified session.
|
|
211
340
|
*/
|
|
212
341
|
async cancel(sessionId: string): Promise<void> {
|
|
213
|
-
|
|
214
|
-
throw new Error(`ACP agent "${this.agentId}" is not spawned`);
|
|
215
|
-
}
|
|
342
|
+
const connection = this.requireConnection();
|
|
216
343
|
|
|
217
344
|
log.info(
|
|
218
345
|
{ agentId: this.agentId, sessionId },
|
|
219
346
|
"Cancelling ACP session prompt",
|
|
220
347
|
);
|
|
221
348
|
|
|
222
|
-
await
|
|
349
|
+
await connection.cancel({ sessionId });
|
|
223
350
|
}
|
|
224
351
|
|
|
225
352
|
/**
|
|
@@ -234,6 +361,7 @@ export class AcpAgentProcess {
|
|
|
234
361
|
}
|
|
235
362
|
this.connection = null;
|
|
236
363
|
this.initializeResponse = null;
|
|
364
|
+
this.spawnedEnv = null;
|
|
237
365
|
}
|
|
238
366
|
|
|
239
367
|
/**
|
|
@@ -265,5 +393,6 @@ export class AcpAgentProcess {
|
|
|
265
393
|
this.proc = null;
|
|
266
394
|
this.connection = null;
|
|
267
395
|
this.initializeResponse = null;
|
|
396
|
+
this.spawnedEnv = null;
|
|
268
397
|
}
|
|
269
398
|
}
|
|
@@ -93,6 +93,32 @@ async function injectCredential(
|
|
|
93
93
|
return result.success ? undefined : result.reason;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Inject an OPTIONAL credential: skip when the env var is already set
|
|
98
|
+
* (config.json override wins), and treat a vault miss as non-fatal — the
|
|
99
|
+
* adapter has its own login fallback, so spawning without the key is fine.
|
|
100
|
+
*/
|
|
101
|
+
async function injectOptionalCredential(
|
|
102
|
+
env: Record<string, string>,
|
|
103
|
+
field: string,
|
|
104
|
+
envVar: string,
|
|
105
|
+
usageDescription: string,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
if (env[envVar]) return;
|
|
108
|
+
const missReason = await injectCredential(
|
|
109
|
+
env,
|
|
110
|
+
field,
|
|
111
|
+
envVar,
|
|
112
|
+
usageDescription,
|
|
113
|
+
);
|
|
114
|
+
if (missReason !== undefined) {
|
|
115
|
+
log.debug(
|
|
116
|
+
{ reason: missReason },
|
|
117
|
+
`${envVar} unavailable from the vault; spawning without it`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
96
122
|
/**
|
|
97
123
|
* Returns a NEW config with any required credentials merged into `env`.
|
|
98
124
|
* Does NOT mutate the input. Throws `FailedDependencyError` if a required
|
|
@@ -123,6 +149,13 @@ async function injectCredential(
|
|
|
123
149
|
* ways (config.json override wins, vault field `gemini_api_key` second),
|
|
124
150
|
* but it is OPTIONAL: the Gemini CLI supports its own OAuth login, so a
|
|
125
151
|
* vault miss proceeds without the key instead of failing the spawn.
|
|
152
|
+
*
|
|
153
|
+
* For `codex-acp` the env vars are `OPENAI_API_KEY` (vault field
|
|
154
|
+
* `acp/openai_api_key`) and `CODEX_API_KEY` (vault field
|
|
155
|
+
* `acp/codex_api_key`), provisioned the same two ways (config.json
|
|
156
|
+
* override wins, vault second). Both are OPTIONAL: codex also supports
|
|
157
|
+
* ChatGPT login (`codex login` pre-seeding `auth.json` in the workspace),
|
|
158
|
+
* so a vault miss proceeds without the key instead of failing the spawn.
|
|
126
159
|
*/
|
|
127
160
|
export async function prepareAgentEnv(
|
|
128
161
|
agentConfig: AcpAgentConfig,
|
|
@@ -150,22 +183,29 @@ export async function prepareAgentEnv(
|
|
|
150
183
|
);
|
|
151
184
|
}
|
|
152
185
|
} else if (adapterCommand === "gemini") {
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
await injectOptionalCredential(
|
|
187
|
+
env,
|
|
188
|
+
"gemini_api_key",
|
|
189
|
+
"GEMINI_API_KEY",
|
|
190
|
+
"Gemini API key for ACP agent authentication",
|
|
191
|
+
);
|
|
192
|
+
} else if (adapterCommand === "codex-acp") {
|
|
193
|
+
// The two reads target independent vault fields and write disjoint env
|
|
194
|
+
// keys, so running them concurrently is safe.
|
|
195
|
+
await Promise.all([
|
|
196
|
+
injectOptionalCredential(
|
|
155
197
|
env,
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
}
|
|
198
|
+
"openai_api_key",
|
|
199
|
+
"OPENAI_API_KEY",
|
|
200
|
+
"OpenAI API key for Codex ACP agent authentication",
|
|
201
|
+
),
|
|
202
|
+
injectOptionalCredential(
|
|
203
|
+
env,
|
|
204
|
+
"codex_api_key",
|
|
205
|
+
"CODEX_API_KEY",
|
|
206
|
+
"Codex API key for Codex ACP agent authentication",
|
|
207
|
+
),
|
|
208
|
+
]);
|
|
169
209
|
}
|
|
170
210
|
|
|
171
211
|
return { ...agentConfig, env };
|
package/src/agent/loop.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
estimateToolsTokens,
|
|
12
12
|
getCalibrationProviderKey,
|
|
13
13
|
} from "../context/token-estimator.js";
|
|
14
|
+
import { spoolAndStubOversizedToolResults } from "../context/tool-result-spool.js";
|
|
14
15
|
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
15
16
|
import { parseActualTokensFromError } from "../daemon/parse-actual-tokens-from-error.js";
|
|
16
17
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
@@ -298,25 +299,27 @@ export type AgentEvent =
|
|
|
298
299
|
/** The running history before injection stripping and compaction. */
|
|
299
300
|
messages: Message[];
|
|
300
301
|
}
|
|
301
|
-
| {
|
|
302
|
+
| ({
|
|
302
303
|
/**
|
|
303
304
|
* Emitted after the loop's inline mid-loop compaction pipeline runs,
|
|
304
305
|
* immediately before re-injection — whether or not the pipeline actually
|
|
305
|
-
* compacted.
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
306
|
+
* compacted. Carries the pipeline's `ContextWindowResult` unnested into
|
|
307
|
+
* the event, so `messages` here is the pipeline's output history. The
|
|
308
|
+
* pre-compaction history lives on the paired `context_compacting` start
|
|
309
|
+
* event (correlated by `compactionId`); consumers that need the
|
|
310
|
+
* stripped pre-compaction base re-derive it from the start event via
|
|
311
|
+
* `stripInjectionsForCompaction`.
|
|
312
|
+
*
|
|
313
|
+
* The daemon's event dispatcher commits the stripped pre-compaction
|
|
314
|
+
* base as the conversation's durable message state, so re-injection
|
|
315
|
+
* (the post-compaction hook) re-applies injections onto the stripped
|
|
316
|
+
* base rather than stacking on top of the still-injected messages.
|
|
317
|
+
* When `compacted` is set it additionally commits the durable
|
|
318
|
+
* compaction result (DB-record fields, graph-memory side effects, SSE)
|
|
319
|
+
* and projects Slack provenance from the pre-compaction base.
|
|
313
320
|
*
|
|
314
321
|
* Treated as a critical event: a failed durable commit re-throws so the
|
|
315
322
|
* turn aborts rather than re-injecting against half-applied state.
|
|
316
|
-
*
|
|
317
|
-
* `messages` is the stripped pre-compaction history the summary was
|
|
318
|
-
* built from; the dispatcher uses it to project Slack provenance onto
|
|
319
|
-
* the compacted result.
|
|
320
323
|
*/
|
|
321
324
|
type: "compaction_completed";
|
|
322
325
|
/** Correlates this end event with its `context_compacting` pair. */
|
|
@@ -330,9 +333,7 @@ export type AgentEvent =
|
|
|
330
333
|
startedAt: number;
|
|
331
334
|
/** Epoch ms when the compaction pipeline returned. */
|
|
332
335
|
finishedAt: number;
|
|
333
|
-
|
|
334
|
-
messages: Message[];
|
|
335
|
-
}
|
|
336
|
+
} & ContextWindowResult)
|
|
336
337
|
| {
|
|
337
338
|
/**
|
|
338
339
|
* Emitted right after the loop strips runtime injections from the
|
|
@@ -595,6 +596,16 @@ export interface AgentLoopConstructorOptions {
|
|
|
595
596
|
* post-compaction re-injection resolve the live conversation through.
|
|
596
597
|
*/
|
|
597
598
|
conversationId: string;
|
|
599
|
+
/**
|
|
600
|
+
* Resolve the conversation's on-disk directory, used to spool oversized
|
|
601
|
+
* tool results to `.tool-results/` and swap the inline copy for the
|
|
602
|
+
* post-turn pass's stub at result time — before the result joins history,
|
|
603
|
+
* so the provider-bound prefix stays append-only for prompt caching.
|
|
604
|
+
* Returns `null` while the directory cannot be resolved (e.g. the
|
|
605
|
+
* conversation row is not yet persisted); the loop then skips the
|
|
606
|
+
* result-time pass and the post-turn truncation covers the turn instead.
|
|
607
|
+
*/
|
|
608
|
+
resolveConversationDir?: () => string | null;
|
|
598
609
|
}
|
|
599
610
|
|
|
600
611
|
export class AgentLoop {
|
|
@@ -616,6 +627,9 @@ export class AgentLoop {
|
|
|
616
627
|
*/
|
|
617
628
|
private readonly conversationId: string;
|
|
618
629
|
|
|
630
|
+
/** See {@link AgentLoopConstructorOptions.resolveConversationDir}. */
|
|
631
|
+
private readonly resolveConversationDir: (() => string | null) | null;
|
|
632
|
+
|
|
619
633
|
/**
|
|
620
634
|
* Loop-held compaction circuit breaker. The loop has a 1:1 lifetime with its
|
|
621
635
|
* conversation, so it is the source of truth for the cross-turn failure
|
|
@@ -635,6 +649,7 @@ export class AgentLoop {
|
|
|
635
649
|
resolveTools,
|
|
636
650
|
resolveSystemPrompt,
|
|
637
651
|
conversationId,
|
|
652
|
+
resolveConversationDir,
|
|
638
653
|
} = options;
|
|
639
654
|
this.provider = provider;
|
|
640
655
|
this.systemPrompt = systemPrompt;
|
|
@@ -644,6 +659,7 @@ export class AgentLoop {
|
|
|
644
659
|
this.resolveSystemPrompt = resolveSystemPrompt ?? null;
|
|
645
660
|
this.toolExecutor = toolExecutor ?? null;
|
|
646
661
|
this.conversationId = conversationId;
|
|
662
|
+
this.resolveConversationDir = resolveConversationDir ?? null;
|
|
647
663
|
this.compactionCircuit = new CompactionCircuit(this.conversationId);
|
|
648
664
|
}
|
|
649
665
|
|
|
@@ -782,10 +798,10 @@ export class AgentLoop {
|
|
|
782
798
|
onEvent,
|
|
783
799
|
);
|
|
784
800
|
}
|
|
785
|
-
// Emit unconditionally: the dispatcher commits the stripped
|
|
786
|
-
//
|
|
787
|
-
// reads it), and runs
|
|
788
|
-
// `
|
|
801
|
+
// Emit unconditionally: the dispatcher commits the stripped pre-compaction
|
|
802
|
+
// base (re-derived from the start event) as the durable message base
|
|
803
|
+
// whether or not the pipeline compacted (re-injection reads it), and runs
|
|
804
|
+
// the durable compaction commit only when `compacted`.
|
|
789
805
|
await onEvent({
|
|
790
806
|
type: "compaction_completed",
|
|
791
807
|
compactionId,
|
|
@@ -793,8 +809,7 @@ export class AgentLoop {
|
|
|
793
809
|
trigger,
|
|
794
810
|
startedAt,
|
|
795
811
|
finishedAt: Date.now(),
|
|
796
|
-
|
|
797
|
-
messages: rawHistory,
|
|
812
|
+
...compactResult,
|
|
798
813
|
});
|
|
799
814
|
const exhausted = compactResult.exhausted ?? false;
|
|
800
815
|
const autoCompressApplied = compactResult.autoCompressApplied ?? false;
|
|
@@ -886,6 +901,19 @@ export class AgentLoop {
|
|
|
886
901
|
let overflowAutoCompressApplied = false;
|
|
887
902
|
const rlog = log.child({ requestId });
|
|
888
903
|
|
|
904
|
+
// Conversation directory for the result-time tool-result spool/stub pass.
|
|
905
|
+
// Resolved once per run; `null` disables the pass for this run and the
|
|
906
|
+
// post-turn truncation covers the turn instead.
|
|
907
|
+
let conversationDir: string | null = null;
|
|
908
|
+
try {
|
|
909
|
+
conversationDir = this.resolveConversationDir?.() ?? null;
|
|
910
|
+
} catch (err) {
|
|
911
|
+
rlog.warn(
|
|
912
|
+
{ err },
|
|
913
|
+
"Resolving conversation dir for tool-result spooling failed (non-fatal)",
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
889
917
|
// Resolve the inference-profile override that applies right now. The
|
|
890
918
|
// optional resolver lets a turn observe a confirmed mid-turn profile switch
|
|
891
919
|
// before the next model call; absent a resolver the turn-start value holds.
|
|
@@ -1724,11 +1752,39 @@ export class AgentLoop {
|
|
|
1724
1752
|
}),
|
|
1725
1753
|
);
|
|
1726
1754
|
|
|
1755
|
+
// Spool oversized results to `.tool-results/` and swap the inline
|
|
1756
|
+
// copy for the post-turn pass's stub now — on the raw blocks, before
|
|
1757
|
+
// the post-tool-use hooks, event emission, and history append. Running
|
|
1758
|
+
// ahead of the hooks means the spooled file holds the tool's full
|
|
1759
|
+
// output rather than the truncate plugin's tail-dropped copy, and the
|
|
1760
|
+
// hooks then see the stub. Stubbing before the first send keeps the
|
|
1761
|
+
// provider-bound history strictly append-only (rewriting an earlier
|
|
1762
|
+
// message between calls would invalidate the prompt-cache prefix on
|
|
1763
|
+
// every iteration).
|
|
1764
|
+
if (conversationDir) {
|
|
1765
|
+
const toolNameByUseId = new Map(
|
|
1766
|
+
toolUseBlocks.map((tu) => [tu.id, tu.name]),
|
|
1767
|
+
);
|
|
1768
|
+
try {
|
|
1769
|
+
spoolAndStubOversizedToolResults(rawResultBlocks, {
|
|
1770
|
+
conversationDir,
|
|
1771
|
+
toolNameById: (id) => toolNameByUseId.get(id),
|
|
1772
|
+
});
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
rlog.warn(
|
|
1775
|
+
{ err, turn: toolUseTurns },
|
|
1776
|
+
"Spooling oversized tool results to disk failed (non-fatal)",
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1727
1781
|
// Run the `post-tool-use` hook once per tool result, after the tool
|
|
1728
1782
|
// returns and before the result joins the provider-bound history.
|
|
1729
1783
|
// The default tool-result-truncate plugin tail-drops oversized output
|
|
1730
|
-
// to fit the context window
|
|
1731
|
-
//
|
|
1784
|
+
// to fit the context window (spool-stubbed results are already tiny;
|
|
1785
|
+
// spool-exempt ones still rely on it); user hooks can swap in a
|
|
1786
|
+
// smarter strategy (e.g. a summariser) or observe results for side
|
|
1787
|
+
// effects.
|
|
1732
1788
|
const contextWindowTokens =
|
|
1733
1789
|
resolveContextWindow?.().maxInputTokens ??
|
|
1734
1790
|
this.config.maxInputTokens ??
|
|
@@ -1746,6 +1802,7 @@ export class AgentLoop {
|
|
|
1746
1802
|
toolResponse: block as ToolResultContent,
|
|
1747
1803
|
messages: history,
|
|
1748
1804
|
additionalContext: null,
|
|
1805
|
+
model: response.model,
|
|
1749
1806
|
maxInputTokens: contextWindowTokens,
|
|
1750
1807
|
logger: rlog,
|
|
1751
1808
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `usage_progress` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Emitted after each LLM call with per-call token deltas and estimated
|
|
5
|
+
* cost. Clients accumulate these additively for live-updating usage
|
|
6
|
+
* metrics (e.g. inline subagent token counters). A UI-only hint — it
|
|
7
|
+
* does not persist to the DB or affect billing.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `usage_update`, which carries the conversation's running
|
|
10
|
+
* totals, this event carries only the deltas for a single LLM call.
|
|
11
|
+
*
|
|
12
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
13
|
+
* directly from this file; external consumers import via
|
|
14
|
+
* `@vellumai/assistant-api`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
export const UsageProgressEventSchema = z.object({
|
|
20
|
+
type: z.literal("usage_progress"),
|
|
21
|
+
conversationId: z.string(),
|
|
22
|
+
inputTokens: z.number(),
|
|
23
|
+
outputTokens: z.number(),
|
|
24
|
+
estimatedCost: z.number(),
|
|
25
|
+
model: z.string(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type UsageProgressEvent = z.infer<typeof UsageProgressEventSchema>;
|
package/src/api/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { UISurfaceCompleteEventSchema } from "./events/ui-surface-complete.js";
|
|
|
49
49
|
import { UISurfaceDismissEventSchema } from "./events/ui-surface-dismiss.js";
|
|
50
50
|
import { UISurfaceShowEventSchema } from "./events/ui-surface-show.js";
|
|
51
51
|
import { UISurfaceUpdateEventSchema } from "./events/ui-surface-update.js";
|
|
52
|
+
import { UsageProgressEventSchema } from "./events/usage-progress.js";
|
|
52
53
|
import { UsageUpdateEventSchema } from "./events/usage-update.js";
|
|
53
54
|
import { UserMessageEchoEventSchema } from "./events/user-message-echo.js";
|
|
54
55
|
|
|
@@ -310,6 +311,10 @@ export {
|
|
|
310
311
|
type UISurfaceUpdateEvent,
|
|
311
312
|
UISurfaceUpdateEventSchema,
|
|
312
313
|
} from "./events/ui-surface-update.js";
|
|
314
|
+
export {
|
|
315
|
+
type UsageProgressEvent,
|
|
316
|
+
UsageProgressEventSchema,
|
|
317
|
+
} from "./events/usage-progress.js";
|
|
313
318
|
export {
|
|
314
319
|
type UsageUpdateEvent,
|
|
315
320
|
UsageUpdateEventSchema,
|
|
@@ -497,6 +502,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
497
502
|
UISurfaceDismissEventSchema,
|
|
498
503
|
UISurfaceShowEventSchema,
|
|
499
504
|
UISurfaceUpdateEventSchema,
|
|
505
|
+
UsageProgressEventSchema,
|
|
500
506
|
UsageUpdateEventSchema,
|
|
501
507
|
UserMessageEchoEventSchema,
|
|
502
508
|
]);
|
|
@@ -52,6 +52,8 @@ mock.module("../util/platform.js", () => ({
|
|
|
52
52
|
getWorkspaceRoutesDir: () => workspacePath("routes"),
|
|
53
53
|
vellumRoot: () => testWorkspaceDir,
|
|
54
54
|
getDataDir: () => workspacePath("data"),
|
|
55
|
+
getConfigQuarantineNoticePath: () =>
|
|
56
|
+
workspacePath("data", "config-quarantine-notice.json"),
|
|
55
57
|
getDbPath: () => workspacePath("data", "db", "assistant.db"),
|
|
56
58
|
ensureDataDir: () => {
|
|
57
59
|
mkdirSync(workspacePath("data", "db"), { recursive: true });
|