@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.
Files changed (214) hide show
  1. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  2. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  3. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  4. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  5. package/openapi.yaml +140 -14
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  8. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  9. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  10. package/src/__tests__/assistant-feature-flags-integration.test.ts +10 -0
  11. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  12. package/src/__tests__/attachments-store.test.ts +130 -68
  13. package/src/__tests__/attachments.test.ts +36 -36
  14. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  15. package/src/__tests__/consult-deadline.test.ts +65 -0
  16. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  17. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  18. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  19. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  20. package/src/__tests__/conversation-store.test.ts +26 -10
  21. package/src/__tests__/cu-unified-flow.test.ts +75 -11
  22. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  23. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  24. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  25. package/src/__tests__/disk-usage.test.ts +35 -20
  26. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  27. package/src/__tests__/file-list-tool.test.ts +12 -10
  28. package/src/__tests__/file-ops-service.test.ts +76 -40
  29. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  30. package/src/__tests__/filesystem-tools.test.ts +82 -66
  31. package/src/__tests__/identity-routes.test.ts +13 -13
  32. package/src/__tests__/image-conversion.test.ts +29 -26
  33. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  34. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  35. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  36. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  37. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  38. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  39. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  40. package/src/__tests__/list-messages-queued.test.ts +6 -6
  41. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  42. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  43. package/src/__tests__/llm-resolver-override-or-default.test.ts +0 -79
  44. package/src/__tests__/llm-resolver.test.ts +0 -2
  45. package/src/__tests__/llm-schema.test.ts +0 -36
  46. package/src/__tests__/llm-usage-store.test.ts +55 -0
  47. package/src/__tests__/migration-export-http.test.ts +1 -1
  48. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  49. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  50. package/src/__tests__/migration-validate-http.test.ts +1 -1
  51. package/src/__tests__/persist-media-references.test.ts +2 -2
  52. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  53. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  54. package/src/__tests__/profiler-routes.test.ts +13 -13
  55. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  56. package/src/__tests__/server-history-render.test.ts +97 -7
  57. package/src/__tests__/size-guard.test.ts +8 -8
  58. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  59. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  60. package/src/__tests__/skills-install-staging.test.ts +15 -6
  61. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  62. package/src/__tests__/subagent-disposal.test.ts +10 -0
  63. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  64. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  65. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  66. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  67. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  68. package/src/__tests__/subagent-persistence.test.ts +141 -0
  69. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  70. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  71. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  72. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  73. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  74. package/src/__tests__/subagent-tool-gate-mode.test.ts +215 -0
  75. package/src/__tests__/subagent-tools.test.ts +1934 -97
  76. package/src/__tests__/tools-audio-read.test.ts +10 -10
  77. package/src/__tests__/tools-get-route.test.ts +85 -12
  78. package/src/__tests__/tools-list-cli.test.ts +171 -0
  79. package/src/agent/attachments.ts +26 -24
  80. package/src/agent/image-optimize.ts +6 -6
  81. package/src/agent/loop.ts +21 -9
  82. package/src/agent/message-types.ts +3 -3
  83. package/src/api/events/question-answered.ts +53 -0
  84. package/src/api/events/tool-result.ts +7 -0
  85. package/src/api/index.ts +6 -0
  86. package/src/api/responses/conversation-message.ts +11 -0
  87. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  88. package/src/cli/commands/tools.help.ts +4 -2
  89. package/src/cli/commands/tools.ts +38 -0
  90. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  91. package/src/config/__tests__/sync-gated-profiles.test.ts +0 -25
  92. package/src/config/bundled-skills/AGENTS.md +1 -1
  93. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  94. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  95. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  96. package/src/config/feature-flag-registry.json +16 -8
  97. package/src/config/llm-resolver.ts +10 -44
  98. package/src/config/profile-tool-support.ts +110 -0
  99. package/src/config/schemas/llm.ts +0 -20
  100. package/src/config/sync-gated-profiles.ts +0 -11
  101. package/src/context/compactor.ts +4 -4
  102. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  103. package/src/daemon/conversation-agent-loop-handlers.ts +102 -57
  104. package/src/daemon/conversation-attachments.ts +1 -1
  105. package/src/daemon/conversation-messaging.ts +31 -26
  106. package/src/daemon/conversation-process.ts +23 -14
  107. package/src/daemon/conversation-surfaces.ts +71 -6
  108. package/src/daemon/conversation-tool-setup.ts +54 -2
  109. package/src/daemon/conversation.ts +17 -2
  110. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  111. package/src/daemon/disk-pressure-guard.ts +3 -3
  112. package/src/daemon/handlers/shared.ts +12 -0
  113. package/src/daemon/handlers/skills.ts +1 -1
  114. package/src/daemon/install-assistant-command.ts +322 -0
  115. package/src/daemon/lifecycle.ts +3 -3
  116. package/src/daemon/persist-media-references.ts +23 -14
  117. package/src/daemon/process-message.ts +6 -6
  118. package/src/daemon/tool-setup-types.ts +31 -2
  119. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  120. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  121. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  122. package/src/live-voice/live-voice-archive.ts +13 -13
  123. package/src/live-voice/live-voice-session.ts +6 -0
  124. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  125. package/src/monitoring/db-integrity-sample.ts +6 -1
  126. package/src/monitoring/resource-sampler.ts +21 -5
  127. package/src/permissions/question-prompter.test.ts +4 -0
  128. package/src/permissions/question-prompter.ts +21 -4
  129. package/src/persistence/attachments-store.ts +15 -15
  130. package/src/persistence/conversation-bootstrap.ts +17 -0
  131. package/src/persistence/conversation-crud.ts +10 -0
  132. package/src/persistence/llm-usage-store.ts +22 -0
  133. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  134. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  135. package/src/persistence/schema/conversations.ts +22 -0
  136. package/src/persistence/steps.ts +8 -0
  137. package/src/persistence/subagent-store.ts +148 -6
  138. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  139. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  140. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  141. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  142. package/src/plugins/defaults/index.ts +1 -1
  143. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  144. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  145. package/src/providers/anthropic/client.ts +6 -4
  146. package/src/providers/gemini/client.ts +5 -5
  147. package/src/providers/media-resolve.ts +21 -11
  148. package/src/providers/openai/chat-completions-provider.ts +4 -4
  149. package/src/providers/openai/responses-provider.ts +3 -3
  150. package/src/providers/retry.ts +25 -0
  151. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -48
  152. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +0 -5
  153. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +13 -1
  154. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  155. package/src/runtime/routes/archive-utils.ts +16 -12
  156. package/src/runtime/routes/attachment-routes.ts +13 -6
  157. package/src/runtime/routes/conversation-query-routes.ts +0 -33
  158. package/src/runtime/routes/conversation-routes.ts +194 -183
  159. package/src/runtime/routes/identity-routes.ts +6 -6
  160. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  161. package/src/runtime/routes/inference-profiles-routes.ts +1 -10
  162. package/src/runtime/routes/llm-call-sites-routes.ts +5 -2
  163. package/src/runtime/routes/log-export-routes.ts +1 -1
  164. package/src/runtime/routes/profiler-routes.ts +4 -2
  165. package/src/runtime/routes/question-routes.ts +28 -10
  166. package/src/runtime/routes/settings-routes.ts +41 -13
  167. package/src/skills/catalog-install.ts +20 -7
  168. package/src/skills/skillssh-registry.ts +1 -1
  169. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  170. package/src/subagent/consult-prompt.ts +3 -1
  171. package/src/subagent/consult-transcript.ts +1 -1
  172. package/src/subagent/manager.ts +279 -42
  173. package/src/subagent/progress-events.ts +35 -0
  174. package/src/subagent/role-resolution.ts +88 -0
  175. package/src/subagent/types.ts +295 -67
  176. package/src/subagent/validate-allowlists.ts +3 -4
  177. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  178. package/src/telemetry/telemetry-event-sources.ts +6 -0
  179. package/src/telemetry/telemetry-wire-source.json +1 -1
  180. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  181. package/src/telemetry/types.ts +20 -0
  182. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  183. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  184. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  185. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  186. package/src/tools/browser/browser-execution.ts +1 -1
  187. package/src/tools/executor.ts +7 -6
  188. package/src/tools/filesystem/edit.ts +1 -1
  189. package/src/tools/filesystem/list.ts +1 -1
  190. package/src/tools/filesystem/read.ts +1 -1
  191. package/src/tools/filesystem/search.ts +23 -21
  192. package/src/tools/filesystem/write.ts +4 -1
  193. package/src/tools/host-filesystem/edit.ts +1 -1
  194. package/src/tools/host-filesystem/read.ts +1 -1
  195. package/src/tools/host-filesystem/write.ts +4 -1
  196. package/src/tools/permission-checker.ts +8 -3
  197. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  198. package/src/tools/shared/filesystem/file-ops-service.ts +140 -113
  199. package/src/tools/shared/filesystem/image-read.ts +12 -12
  200. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  201. package/src/tools/shared/input-misuse.ts +72 -0
  202. package/src/tools/skills/skill-tool-factory.ts +14 -1
  203. package/src/tools/subagent/consult-deadline.ts +12 -7
  204. package/src/tools/subagent/read.ts +66 -4
  205. package/src/tools/subagent/spawn.ts +518 -50
  206. package/src/tools/types.ts +9 -0
  207. package/src/tools/ui-surface/visual-validation.ts +2 -0
  208. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  209. package/src/usage/subagent-attribution.ts +108 -0
  210. package/src/util/disk-usage.ts +17 -13
  211. package/src/util/image-conversion.ts +20 -13
  212. package/src/util/worker-process.ts +7 -1
  213. package/src/workspace/git-service.ts +10 -9
  214. 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: pure
5
- * environment types and constants with no runtime dependencies. It must
6
- * stay a leaf so any consumer (CLI, assistant, local-mode host) can depend
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 pure types/constants leaf and must\n" +
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 — the single source of truth for Vellum's known
3
- * deployment environments (names, platform/web URLs, per-service port
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
- * Intentionally free of runtime dependencies: pure types and constants only.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.11.1-dev.202608042001.feb8dd6",
3
+ "version": "0.11.1-dev.202608042130.2b7da8d",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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 — the minimum floor is rejection-path only", () => {
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
+ });