@vellumai/assistant 0.11.1-dev.202608042001.feb8dd6 → 0.11.1-dev.202608042130.2b7da8d
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/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/openapi.yaml +140 -14
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +10 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/cu-unified-flow.test.ts +75 -11
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +76 -40
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +6 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +0 -79
- package/src/__tests__/llm-resolver.test.ts +0 -2
- package/src/__tests__/llm-schema.test.ts +0 -36
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +215 -0
- package/src/__tests__/subagent-tools.test.ts +1934 -97
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/agent/attachments.ts +26 -24
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +21 -9
- package/src/agent/message-types.ts +3 -3
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +0 -25
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/llm-resolver.ts +10 -44
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/schemas/llm.ts +0 -20
- package/src/config/sync-gated-profiles.ts +0 -11
- package/src/context/compactor.ts +4 -4
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +102 -57
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-messaging.ts +31 -26
- package/src/daemon/conversation-process.ts +23 -14
- package/src/daemon/conversation-surfaces.ts +71 -6
- package/src/daemon/conversation-tool-setup.ts +54 -2
- package/src/daemon/conversation.ts +17 -2
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +3 -3
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +31 -2
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-session.ts +6 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +10 -0
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/schema/conversations.ts +22 -0
- package/src/persistence/steps.ts +8 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/anthropic/client.ts +6 -4
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +3 -3
- package/src/providers/retry.ts +25 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -48
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +13 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/conversation-query-routes.ts +0 -33
- package/src/runtime/routes/conversation-routes.ts +194 -183
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inference-profiles-routes.ts +1 -10
- package/src/runtime/routes/llm-call-sites-routes.ts +5 -2
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +279 -42
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +295 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +20 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +1 -1
- package/src/tools/host-filesystem/write.ts +4 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +140 -113
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +518 -50
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +10 -9
- package/src/daemon/install-symlink.ts +0 -207
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for reading an install layout: telling a repo checkout from an
|
|
3
|
+
* installed package, and locating the `assistant` command that ships with an
|
|
4
|
+
* install.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { findAssistantCommand, isRepoCheckoutPath } from "../index.js";
|
|
13
|
+
|
|
14
|
+
let root: string;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
root = mkdtempSync(join(tmpdir(), "install-layout-"));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
rmSync(root, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Create `<nodeModulesDir>/.bin/assistant` and return its path. */
|
|
25
|
+
function seedCommand(nodeModulesDir: string): string {
|
|
26
|
+
const binDir = join(nodeModulesDir, ".bin");
|
|
27
|
+
mkdirSync(binDir, { recursive: true });
|
|
28
|
+
const command = join(binDir, "assistant");
|
|
29
|
+
writeFileSync(command, "#!/usr/bin/env bun\n");
|
|
30
|
+
return command;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("isRepoCheckoutPath", () => {
|
|
34
|
+
test("an installed package is not a checkout", () => {
|
|
35
|
+
expect(
|
|
36
|
+
isRepoCheckoutPath(
|
|
37
|
+
"/Users/x/Library/Application Support/@vellumai/macos/cli/latest/node_modules/@vellumai/assistant/src/index.ts",
|
|
38
|
+
),
|
|
39
|
+
).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("a repo checkout is", () => {
|
|
43
|
+
expect(
|
|
44
|
+
isRepoCheckoutPath("/Users/x/dev/vellum/assistant/src/index.ts"),
|
|
45
|
+
).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("a directory merely named node_modules-ish is not enough", () => {
|
|
49
|
+
expect(
|
|
50
|
+
isRepoCheckoutPath("/Users/x/node_modules_backup/a/src/index.ts"),
|
|
51
|
+
).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("findAssistantCommand", () => {
|
|
56
|
+
test("finds the command shipped beside an installed package", () => {
|
|
57
|
+
const nodeModules = join(root, "cli", "latest", "node_modules");
|
|
58
|
+
const command = seedCommand(nodeModules);
|
|
59
|
+
const path = join(nodeModules, "@vellumai", "assistant", "src", "index.ts");
|
|
60
|
+
|
|
61
|
+
expect(findAssistantCommand(path)).toBe(command);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("prefers a nested install over a hoisted one", () => {
|
|
65
|
+
const outer = join(root, "node_modules");
|
|
66
|
+
const outerCommand = seedCommand(outer);
|
|
67
|
+
const inner = join(outer, "vellum", "node_modules");
|
|
68
|
+
const innerCommand = seedCommand(inner);
|
|
69
|
+
const path = join(inner, "@vellumai", "assistant", "src", "index.ts");
|
|
70
|
+
|
|
71
|
+
expect(findAssistantCommand(path)).toBe(innerCommand);
|
|
72
|
+
expect(innerCommand).not.toBe(outerCommand);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("falls back to a hoisted install when the nested one has no command", () => {
|
|
76
|
+
const outer = join(root, "node_modules");
|
|
77
|
+
const outerCommand = seedCommand(outer);
|
|
78
|
+
const inner = join(outer, "vellum", "node_modules");
|
|
79
|
+
mkdirSync(inner, { recursive: true });
|
|
80
|
+
const path = join(inner, "@vellumai", "assistant", "src", "index.ts");
|
|
81
|
+
|
|
82
|
+
expect(findAssistantCommand(path)).toBe(outerCommand);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("returns null for a repo checkout", () => {
|
|
86
|
+
expect(
|
|
87
|
+
findAssistantCommand(join(root, "assistant", "src", "index.ts")),
|
|
88
|
+
).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("returns null when the install ships no assistant command", () => {
|
|
92
|
+
const nodeModules = join(root, "node_modules");
|
|
93
|
+
mkdirSync(join(nodeModules, ".bin"), { recursive: true });
|
|
94
|
+
const path = join(nodeModules, "@vellumai", "assistant", "src", "index.ts");
|
|
95
|
+
|
|
96
|
+
expect(findAssistantCommand(path)).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package boundary tests for @vellumai/environments.
|
|
3
3
|
*
|
|
4
|
-
* This package is the lowest layer of the shared-packages hierarchy:
|
|
5
|
-
* environment types and
|
|
6
|
-
* stay a leaf so any consumer (CLI, assistant,
|
|
7
|
-
* on it without dragging in a dependency tree.
|
|
4
|
+
* This package is the lowest layer of the shared-packages hierarchy:
|
|
5
|
+
* environment types, constants, and small helpers over node builtins, with no
|
|
6
|
+
* runtime dependencies. It must stay a leaf so any consumer (CLI, assistant,
|
|
7
|
+
* local-mode host) can depend on it without dragging in a dependency tree.
|
|
8
8
|
*
|
|
9
9
|
* Enforces that the package:
|
|
10
10
|
* 1. Imports only node builtins and its own relative modules — no `@vellumai/*`
|
|
@@ -79,7 +79,7 @@ describe("package boundary", () => {
|
|
|
79
79
|
throw new Error(
|
|
80
80
|
`Found ${violations.length} forbidden import(s) in @vellumai/environments:\n` +
|
|
81
81
|
violations.map((v) => ` - ${v}`).join("\n") +
|
|
82
|
-
"\n\n@vellumai/environments is a
|
|
82
|
+
"\n\n@vellumai/environments is a dependency-free leaf and must\n" +
|
|
83
83
|
"import only node builtins and its own relative modules.",
|
|
84
84
|
);
|
|
85
85
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @vellumai/environments
|
|
3
|
-
*
|
|
4
|
-
* blocks). Consumed by the CLI, the assistant daemon, and the local-mode
|
|
5
|
-
* host library so the environment list is defined exactly once on the TS
|
|
6
|
-
* side. The Swift client mirrors the same set in `VellumEnvironment.swift`.
|
|
2
|
+
* @vellumai/environments: the single source of truth for how a Vellum install
|
|
3
|
+
* is laid out and which deployment environment it belongs to.
|
|
7
4
|
*
|
|
8
|
-
*
|
|
5
|
+
* Two things live here:
|
|
6
|
+
*
|
|
7
|
+
* - The known deployment environments (names, platform/web URLs, per-service
|
|
8
|
+
* port blocks). The Swift client mirrors the same set in
|
|
9
|
+
* `VellumEnvironment.swift`.
|
|
10
|
+
* - Install layout: whether a runtime is a repo checkout or an installed
|
|
11
|
+
* package, and where the `assistant` command it ships lives.
|
|
12
|
+
*
|
|
13
|
+
* Consumed by the CLI, the assistant daemon, and the local-mode host library
|
|
14
|
+
* so each answer is defined exactly once on the TS side.
|
|
15
|
+
*
|
|
16
|
+
* This is the lowest layer of the shared-packages hierarchy and stays a leaf:
|
|
17
|
+
* types, constants, and small helpers over node builtins, with no runtime
|
|
18
|
+
* dependencies of its own. See `__tests__/package-boundary.test.ts`.
|
|
9
19
|
*/
|
|
10
20
|
export * from "./types.js";
|
|
11
21
|
export * from "./seeds.js";
|
|
22
|
+
export * from "./install-layout.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the layout a Vellum runtime is installed in.
|
|
3
|
+
*
|
|
4
|
+
* The daemon is started either from a developer's repo checkout or from an
|
|
5
|
+
* installed npm package (`<install>/node_modules/@vellumai/assistant/src/
|
|
6
|
+
* index.ts`, which is what the desktop app runs). The CLI needs the layout to
|
|
7
|
+
* decide whether a launch is a dev run and what to put on the daemon's PATH;
|
|
8
|
+
* the daemon needs it to decide what to install as the `assistant` command.
|
|
9
|
+
* Both ask the same two questions, so they ask them here.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { join, sep } from "node:path";
|
|
14
|
+
|
|
15
|
+
const NODE_MODULES_SEGMENT = `${sep}node_modules${sep}`;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether `path` sits in a developer's repo checkout rather than inside an
|
|
19
|
+
* installed package.
|
|
20
|
+
*/
|
|
21
|
+
export function isRepoCheckoutPath(path: string): boolean {
|
|
22
|
+
return !path.includes(NODE_MODULES_SEGMENT);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The `assistant` command shipped with the install that contains `path`, or
|
|
27
|
+
* null when there is none (a repo checkout, or an install whose dependency
|
|
28
|
+
* graph omits the package declaring the bin).
|
|
29
|
+
*
|
|
30
|
+
* Searches innermost `node_modules` outward, since a nested install shadows a
|
|
31
|
+
* hoisted one.
|
|
32
|
+
*/
|
|
33
|
+
export function findAssistantCommand(path: string): string | null {
|
|
34
|
+
let index = path.lastIndexOf(NODE_MODULES_SEGMENT);
|
|
35
|
+
|
|
36
|
+
while (index !== -1) {
|
|
37
|
+
const nodeModulesDir = path.slice(
|
|
38
|
+
0,
|
|
39
|
+
index + NODE_MODULES_SEGMENT.length - 1,
|
|
40
|
+
);
|
|
41
|
+
const command = join(nodeModulesDir, ".bin", "assistant");
|
|
42
|
+
if (existsSync(command)) {
|
|
43
|
+
return command;
|
|
44
|
+
}
|
|
45
|
+
index = path.lastIndexOf(NODE_MODULES_SEGMENT, index - 1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
}
|
package/openapi.yaml
CHANGED
|
@@ -19056,6 +19056,76 @@ paths:
|
|
|
19056
19056
|
- requestId
|
|
19057
19057
|
- entries
|
|
19058
19058
|
additionalProperties: false
|
|
19059
|
+
answeredQuestion:
|
|
19060
|
+
type: object
|
|
19061
|
+
properties:
|
|
19062
|
+
requestId:
|
|
19063
|
+
type: string
|
|
19064
|
+
questions:
|
|
19065
|
+
type: array
|
|
19066
|
+
items:
|
|
19067
|
+
type: object
|
|
19068
|
+
properties:
|
|
19069
|
+
id:
|
|
19070
|
+
type: string
|
|
19071
|
+
question:
|
|
19072
|
+
type: string
|
|
19073
|
+
description:
|
|
19074
|
+
type: string
|
|
19075
|
+
options:
|
|
19076
|
+
type: array
|
|
19077
|
+
items:
|
|
19078
|
+
type: object
|
|
19079
|
+
properties:
|
|
19080
|
+
id:
|
|
19081
|
+
type: string
|
|
19082
|
+
label:
|
|
19083
|
+
type: string
|
|
19084
|
+
description:
|
|
19085
|
+
type: string
|
|
19086
|
+
required:
|
|
19087
|
+
- id
|
|
19088
|
+
- label
|
|
19089
|
+
additionalProperties: false
|
|
19090
|
+
freeTextPlaceholder:
|
|
19091
|
+
type: string
|
|
19092
|
+
required:
|
|
19093
|
+
- id
|
|
19094
|
+
- question
|
|
19095
|
+
- options
|
|
19096
|
+
additionalProperties: false
|
|
19097
|
+
responses:
|
|
19098
|
+
type: array
|
|
19099
|
+
items:
|
|
19100
|
+
type: object
|
|
19101
|
+
properties:
|
|
19102
|
+
questionId:
|
|
19103
|
+
type: string
|
|
19104
|
+
decision:
|
|
19105
|
+
type: string
|
|
19106
|
+
enum:
|
|
19107
|
+
- option
|
|
19108
|
+
- free_text
|
|
19109
|
+
- skipped
|
|
19110
|
+
optionId:
|
|
19111
|
+
type: string
|
|
19112
|
+
text:
|
|
19113
|
+
type: string
|
|
19114
|
+
required:
|
|
19115
|
+
- questionId
|
|
19116
|
+
- decision
|
|
19117
|
+
additionalProperties: false
|
|
19118
|
+
overall:
|
|
19119
|
+
type: string
|
|
19120
|
+
enum:
|
|
19121
|
+
- completed
|
|
19122
|
+
- closed
|
|
19123
|
+
required:
|
|
19124
|
+
- requestId
|
|
19125
|
+
- questions
|
|
19126
|
+
- responses
|
|
19127
|
+
- overall
|
|
19128
|
+
additionalProperties: false
|
|
19059
19129
|
required:
|
|
19060
19130
|
- name
|
|
19061
19131
|
- input
|
|
@@ -19495,6 +19565,76 @@ paths:
|
|
|
19495
19565
|
- requestId
|
|
19496
19566
|
- entries
|
|
19497
19567
|
additionalProperties: false
|
|
19568
|
+
answeredQuestion:
|
|
19569
|
+
type: object
|
|
19570
|
+
properties:
|
|
19571
|
+
requestId:
|
|
19572
|
+
type: string
|
|
19573
|
+
questions:
|
|
19574
|
+
type: array
|
|
19575
|
+
items:
|
|
19576
|
+
type: object
|
|
19577
|
+
properties:
|
|
19578
|
+
id:
|
|
19579
|
+
type: string
|
|
19580
|
+
question:
|
|
19581
|
+
type: string
|
|
19582
|
+
description:
|
|
19583
|
+
type: string
|
|
19584
|
+
options:
|
|
19585
|
+
type: array
|
|
19586
|
+
items:
|
|
19587
|
+
type: object
|
|
19588
|
+
properties:
|
|
19589
|
+
id:
|
|
19590
|
+
type: string
|
|
19591
|
+
label:
|
|
19592
|
+
type: string
|
|
19593
|
+
description:
|
|
19594
|
+
type: string
|
|
19595
|
+
required:
|
|
19596
|
+
- id
|
|
19597
|
+
- label
|
|
19598
|
+
additionalProperties: false
|
|
19599
|
+
freeTextPlaceholder:
|
|
19600
|
+
type: string
|
|
19601
|
+
required:
|
|
19602
|
+
- id
|
|
19603
|
+
- question
|
|
19604
|
+
- options
|
|
19605
|
+
additionalProperties: false
|
|
19606
|
+
responses:
|
|
19607
|
+
type: array
|
|
19608
|
+
items:
|
|
19609
|
+
type: object
|
|
19610
|
+
properties:
|
|
19611
|
+
questionId:
|
|
19612
|
+
type: string
|
|
19613
|
+
decision:
|
|
19614
|
+
type: string
|
|
19615
|
+
enum:
|
|
19616
|
+
- option
|
|
19617
|
+
- free_text
|
|
19618
|
+
- skipped
|
|
19619
|
+
optionId:
|
|
19620
|
+
type: string
|
|
19621
|
+
text:
|
|
19622
|
+
type: string
|
|
19623
|
+
required:
|
|
19624
|
+
- questionId
|
|
19625
|
+
- decision
|
|
19626
|
+
additionalProperties: false
|
|
19627
|
+
overall:
|
|
19628
|
+
type: string
|
|
19629
|
+
enum:
|
|
19630
|
+
- completed
|
|
19631
|
+
- closed
|
|
19632
|
+
required:
|
|
19633
|
+
- requestId
|
|
19634
|
+
- questions
|
|
19635
|
+
- responses
|
|
19636
|
+
- overall
|
|
19637
|
+
additionalProperties: false
|
|
19498
19638
|
required:
|
|
19499
19639
|
- name
|
|
19500
19640
|
- input
|
|
@@ -33307,14 +33447,6 @@ components:
|
|
|
33307
33447
|
anyOf:
|
|
33308
33448
|
- $ref: "#/components/schemas/CallSiteOverrideDraft"
|
|
33309
33449
|
- type: "null"
|
|
33310
|
-
defaultProfileOverrides:
|
|
33311
|
-
type: object
|
|
33312
|
-
propertyNames:
|
|
33313
|
-
type: string
|
|
33314
|
-
additionalProperties:
|
|
33315
|
-
anyOf:
|
|
33316
|
-
- type: string
|
|
33317
|
-
- type: "null"
|
|
33318
33450
|
profileSession:
|
|
33319
33451
|
anyOf:
|
|
33320
33452
|
- type: object
|
|
@@ -34142,12 +34274,6 @@ components:
|
|
|
34142
34274
|
type: string
|
|
34143
34275
|
additionalProperties: false
|
|
34144
34276
|
- type: "null"
|
|
34145
|
-
defaultProfileOverrides:
|
|
34146
|
-
type: object
|
|
34147
|
-
propertyNames:
|
|
34148
|
-
type: string
|
|
34149
|
-
additionalProperties:
|
|
34150
|
-
type: string
|
|
34151
34277
|
profileSession:
|
|
34152
34278
|
type: object
|
|
34153
34279
|
properties:
|
package/package.json
CHANGED
|
@@ -115,9 +115,9 @@ describe("upscaleTargetDimensions", () => {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
describe("upscaleImageToMinimum", () => {
|
|
118
|
-
it("returns null when dimensions are unparseable", () => {
|
|
118
|
+
it("returns null when dimensions are unparseable", async () => {
|
|
119
119
|
expect(
|
|
120
|
-
upscaleImageToMinimum(
|
|
120
|
+
await upscaleImageToMinimum(
|
|
121
121
|
Buffer.from("not an image").toString("base64"),
|
|
122
122
|
"image/png",
|
|
123
123
|
),
|
|
@@ -126,12 +126,12 @@ describe("upscaleImageToMinimum", () => {
|
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
describe("optimizeImageForTransport", () => {
|
|
129
|
-
it("passes an undersized image through unchanged
|
|
129
|
+
it("passes an undersized image through unchanged: the minimum floor is rejection-path only", async () => {
|
|
130
130
|
// The floor is undocumented provider behavior, so pre-send transport
|
|
131
131
|
// never enforces it; only the image-recovery plugin reacts to an actual
|
|
132
132
|
// "Could not process image" rejection.
|
|
133
133
|
const tiny = makePngBase64(16, 14);
|
|
134
|
-
expect(optimizeImageForTransport(tiny, "image/png")).toEqual({
|
|
134
|
+
expect(await optimizeImageForTransport(tiny, "image/png")).toEqual({
|
|
135
135
|
data: tiny,
|
|
136
136
|
mediaType: "image/png",
|
|
137
137
|
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime proxy bills delegated work off `X-Vellum-Subagent-Role` and
|
|
3
|
+
* `X-Vellum-Subagent-Spawn-Mode`. `RetryProvider` resolves both from
|
|
4
|
+
* `config.conversationId`, so the headers only ever materialize in production
|
|
5
|
+
* if `AgentLoop` stamps its own conversation id onto the provider config it
|
|
6
|
+
* sends. These tests drive a real `AgentLoop` through a real `RetryProvider`
|
|
7
|
+
* so a config field dropped at the loop can never pass silently.
|
|
8
|
+
*
|
|
9
|
+
* Subagents run their turns through `ConversationRunner.runAgentLoop` with
|
|
10
|
+
* `callSite: "subagentSpawn"`, which is the shape reproduced here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { AgentLoop } from "../agent/loop.js";
|
|
16
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
17
|
+
import { rawRun } from "../persistence/raw-query.js";
|
|
18
|
+
import { RetryProvider } from "../providers/retry.js";
|
|
19
|
+
import type {
|
|
20
|
+
Message,
|
|
21
|
+
Provider,
|
|
22
|
+
ProviderResponse,
|
|
23
|
+
SendMessageOptions,
|
|
24
|
+
} from "../providers/types.js";
|
|
25
|
+
import { resetSubagentAttributionCacheForTests } from "../usage/subagent-attribution.js";
|
|
26
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
27
|
+
|
|
28
|
+
const userMessage: Message = {
|
|
29
|
+
role: "user",
|
|
30
|
+
content: [{ type: "text", text: "hi" }],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stands in for the transport underneath `RetryProvider`, recording the config
|
|
35
|
+
* it is handed. That config is what would go on the wire, so it is also where
|
|
36
|
+
* the resolved billing headers land.
|
|
37
|
+
*/
|
|
38
|
+
function makeRecordingProvider(name: string): {
|
|
39
|
+
provider: Provider;
|
|
40
|
+
configs: () => Array<Record<string, unknown>>;
|
|
41
|
+
} {
|
|
42
|
+
const configs: Array<Record<string, unknown>> = [];
|
|
43
|
+
const provider: Provider = {
|
|
44
|
+
name,
|
|
45
|
+
async sendMessage(
|
|
46
|
+
_messages: Message[],
|
|
47
|
+
options?: SendMessageOptions,
|
|
48
|
+
): Promise<ProviderResponse> {
|
|
49
|
+
configs.push((options?.config ?? {}) as Record<string, unknown>);
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: "ok" }],
|
|
52
|
+
model: "gpt-sub",
|
|
53
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
54
|
+
stopReason: "end_turn",
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
return { provider, configs: () => configs };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function seedConversation(
|
|
62
|
+
id: string,
|
|
63
|
+
subagentRole: string | null,
|
|
64
|
+
subagentSpawnMode: string | null,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
await initializeDb();
|
|
67
|
+
resetSubagentAttributionCacheForTests();
|
|
68
|
+
rawRun(
|
|
69
|
+
"test:seedSubagentConversation",
|
|
70
|
+
`INSERT OR REPLACE INTO conversations (id, conversation_type, created_at, updated_at, subagent_role, subagent_spawn_mode)
|
|
71
|
+
VALUES (?, 'background', 1000, 1000, ?, ?)`,
|
|
72
|
+
id,
|
|
73
|
+
subagentRole,
|
|
74
|
+
subagentSpawnMode,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Runs one loop turn the way a subagent's conversation runner does. */
|
|
79
|
+
async function runSubagentTurn(
|
|
80
|
+
provider: Provider,
|
|
81
|
+
conversationId: string,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const loop = new AgentLoop({
|
|
84
|
+
provider,
|
|
85
|
+
systemPrompt: "system",
|
|
86
|
+
conversationId,
|
|
87
|
+
config: { maxTokens: 1024 },
|
|
88
|
+
});
|
|
89
|
+
await loop.run({
|
|
90
|
+
requestId: "test-request",
|
|
91
|
+
messages: [userMessage],
|
|
92
|
+
onEvent: () => {},
|
|
93
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
94
|
+
callSite: "subagentSpawn",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
describe("AgentLoop subagent billing-header attribution", () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
setConfig("llm", {
|
|
101
|
+
callSites: { subagentSpawn: { provider: "openai", model: "gpt-sub" } },
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("forwards the spawning role and mode on a subagent conversation's turn", async () => {
|
|
106
|
+
// GIVEN a conversation stamped as an advisor-consult subagent
|
|
107
|
+
await seedConversation("conv-loop-advisor", "advisor", "advisor_consult");
|
|
108
|
+
|
|
109
|
+
// AND a managed-proxy-shaped provider stack (headers forwarded)
|
|
110
|
+
const { provider, configs } = makeRecordingProvider("openai");
|
|
111
|
+
const wrapped = new RetryProvider(provider, {
|
|
112
|
+
forwardUsageAttributionHeaders: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// WHEN that subagent's loop runs a turn
|
|
116
|
+
await runSubagentTurn(wrapped, "conv-loop-advisor");
|
|
117
|
+
|
|
118
|
+
// THEN the transport sees the delegated-work attribution
|
|
119
|
+
expect(configs()).toHaveLength(1);
|
|
120
|
+
const headers = configs()[0]?.usageAttributionHeaders as Record<
|
|
121
|
+
string,
|
|
122
|
+
string
|
|
123
|
+
>;
|
|
124
|
+
expect(headers["X-Vellum-Subagent-Role"]).toBe("advisor");
|
|
125
|
+
expect(headers["X-Vellum-Subagent-Spawn-Mode"]).toBe("advisor_consult");
|
|
126
|
+
expect(headers["X-Vellum-LLM-Call-Site"]).toBe("subagentSpawn");
|
|
127
|
+
|
|
128
|
+
// AND the routing-time id itself never reaches the wire config
|
|
129
|
+
expect(configs()[0]?.conversationId).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("omits the subagent headers on a turn for a plain conversation", async () => {
|
|
133
|
+
// GIVEN a conversation that was not spawned as a subagent
|
|
134
|
+
await seedConversation("conv-loop-plain", null, null);
|
|
135
|
+
|
|
136
|
+
const { provider, configs } = makeRecordingProvider("openai");
|
|
137
|
+
const wrapped = new RetryProvider(provider, {
|
|
138
|
+
forwardUsageAttributionHeaders: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// WHEN its loop runs a turn
|
|
142
|
+
await runSubagentTurn(wrapped, "conv-loop-plain");
|
|
143
|
+
|
|
144
|
+
// THEN the base attribution is present without the delegated-work fields
|
|
145
|
+
const headers = configs()[0]?.usageAttributionHeaders as Record<
|
|
146
|
+
string,
|
|
147
|
+
string
|
|
148
|
+
>;
|
|
149
|
+
expect(headers["X-Vellum-LLM-Call-Site"]).toBe("subagentSpawn");
|
|
150
|
+
expect(headers["X-Vellum-Subagent-Role"]).toBeUndefined();
|
|
151
|
+
expect(headers["X-Vellum-Subagent-Spawn-Mode"]).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("stamps the conversation id on the provider config it sends", async () => {
|
|
155
|
+
// GIVEN a bare recording provider (no `RetryProvider` to consume the field)
|
|
156
|
+
const { provider, configs } = makeRecordingProvider("openai");
|
|
157
|
+
|
|
158
|
+
// WHEN a loop with a conversation id runs a call-site turn
|
|
159
|
+
await runSubagentTurn(provider, "conv-loop-raw");
|
|
160
|
+
|
|
161
|
+
// THEN the id the header lookup depends on is on the config, alongside the
|
|
162
|
+
// mix-expansion seed
|
|
163
|
+
expect(configs()[0]?.conversationId).toBe("conv-loop-raw");
|
|
164
|
+
expect(configs()[0]?.selectionSeed).toBe("conv-loop-raw");
|
|
165
|
+
});
|
|
166
|
+
});
|