@vellumai/assistant 0.12.2 → 0.12.3-staging.1
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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the per-conversation wire surface record
|
|
3
|
+
* (`persistence/conversation-tool-surface.ts`): the send-boundary recorder
|
|
4
|
+
* stores the tools array a live turn sends with the delegation-section state
|
|
5
|
+
* its prompt rendered, fork wakes read both back verbatim, and an unchanged
|
|
6
|
+
* surface is never rewritten.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Database } from "bun:sqlite";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
getConversationToolSurface as getConversationToolSurfaceViaPluginApi,
|
|
16
|
+
getRecordedConversationToolSurface,
|
|
17
|
+
} from "../persistence/conversation-plugin-facade.js";
|
|
18
|
+
import {
|
|
19
|
+
type ConversationToolSurface,
|
|
20
|
+
getConversationToolSurface,
|
|
21
|
+
hashConversationToolSurface,
|
|
22
|
+
recordConversationToolSurface,
|
|
23
|
+
} from "../persistence/conversation-tool-surface.js";
|
|
24
|
+
import { clearStoredDb, setStoredDb } from "../persistence/db-singleton.js";
|
|
25
|
+
import { migrateCreateConversationToolSurfaces } from "../persistence/migrations/378-create-conversation-tool-surfaces.js";
|
|
26
|
+
import { migrateConversationToolSurfacesDelegateIndependentTasks } from "../persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.js";
|
|
27
|
+
import * as schema from "../persistence/schema/index.js";
|
|
28
|
+
import type { ToolDefinition } from "../providers/types.js";
|
|
29
|
+
|
|
30
|
+
let sqlite: Database;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
sqlite = new Database(":memory:");
|
|
34
|
+
sqlite.run("PRAGMA foreign_keys = ON");
|
|
35
|
+
sqlite.run(/*sql*/ `CREATE TABLE conversations (id TEXT PRIMARY KEY)`);
|
|
36
|
+
sqlite.query(`INSERT INTO conversations (id) VALUES (?)`).run("conv-1");
|
|
37
|
+
const db = drizzle(sqlite, { schema });
|
|
38
|
+
migrateCreateConversationToolSurfaces(db);
|
|
39
|
+
migrateConversationToolSurfacesDelegateIndependentTasks(db);
|
|
40
|
+
setStoredDb("main", db, () => sqlite.close());
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
clearStoredDb("main");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const TOOLS: ToolDefinition[] = [
|
|
48
|
+
{
|
|
49
|
+
name: "remember",
|
|
50
|
+
description: "Save a fact",
|
|
51
|
+
input_schema: { type: "object", properties: { content: {} } },
|
|
52
|
+
},
|
|
53
|
+
{ name: "bell_jingle", description: "Ring", input_schema: {} },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const SURFACE: ConversationToolSurface = {
|
|
57
|
+
tools: TOOLS,
|
|
58
|
+
delegateIndependentTasks: true,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function storedRow(): { tools_hash: string; updated_at: number } | null {
|
|
62
|
+
return sqlite
|
|
63
|
+
.query(
|
|
64
|
+
`SELECT tools_hash, updated_at FROM conversation_tool_surfaces WHERE conversation_id = ?`,
|
|
65
|
+
)
|
|
66
|
+
.get("conv-1") as { tools_hash: string; updated_at: number } | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("recordConversationToolSurface", () => {
|
|
70
|
+
test("stores the surface and returns its content hash", () => {
|
|
71
|
+
const hash = recordConversationToolSurface("conv-1", SURFACE);
|
|
72
|
+
|
|
73
|
+
expect(hash).toBe(hashConversationToolSurface(SURFACE));
|
|
74
|
+
expect(storedRow()?.tools_hash).toBe(hash);
|
|
75
|
+
expect(getConversationToolSurface("conv-1")).toEqual(SURFACE);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("replays the exact array, including key order and extra fields", () => {
|
|
79
|
+
const serverTool = {
|
|
80
|
+
type: "web_search_20250305",
|
|
81
|
+
name: "web_search",
|
|
82
|
+
max_uses: 5,
|
|
83
|
+
} as unknown as ToolDefinition;
|
|
84
|
+
const tools = [...TOOLS, serverTool];
|
|
85
|
+
|
|
86
|
+
recordConversationToolSurface("conv-1", {
|
|
87
|
+
tools,
|
|
88
|
+
delegateIndependentTasks: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(JSON.stringify(getConversationToolSurface("conv-1")?.tools)).toBe(
|
|
92
|
+
JSON.stringify(tools),
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("the delegation-section state round-trips, unknown included", () => {
|
|
97
|
+
for (const delegateIndependentTasks of [true, false, null]) {
|
|
98
|
+
recordConversationToolSurface("conv-1", {
|
|
99
|
+
tools: TOOLS,
|
|
100
|
+
delegateIndependentTasks,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(
|
|
104
|
+
getConversationToolSurface("conv-1")?.delegateIndependentTasks,
|
|
105
|
+
).toBe(delegateIndependentTasks);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("an unchanged surface with a known hash skips the write", () => {
|
|
110
|
+
const hash = recordConversationToolSurface("conv-1", SURFACE);
|
|
111
|
+
const before = storedRow()!.updated_at;
|
|
112
|
+
sqlite
|
|
113
|
+
.query(
|
|
114
|
+
`UPDATE conversation_tool_surfaces SET updated_at = ? WHERE conversation_id = ?`,
|
|
115
|
+
)
|
|
116
|
+
.run(before - 1000, "conv-1");
|
|
117
|
+
|
|
118
|
+
recordConversationToolSurface("conv-1", SURFACE, hash);
|
|
119
|
+
|
|
120
|
+
expect(storedRow()!.updated_at).toBe(before - 1000);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("an unchanged surface with no known hash compares against the stored row before writing", () => {
|
|
124
|
+
recordConversationToolSurface("conv-1", SURFACE);
|
|
125
|
+
const marker = 42;
|
|
126
|
+
sqlite
|
|
127
|
+
.query(
|
|
128
|
+
`UPDATE conversation_tool_surfaces SET updated_at = ? WHERE conversation_id = ?`,
|
|
129
|
+
)
|
|
130
|
+
.run(marker, "conv-1");
|
|
131
|
+
|
|
132
|
+
// A freshly loaded conversation knows no hash yet; the stored row matches,
|
|
133
|
+
// so nothing is rewritten.
|
|
134
|
+
recordConversationToolSurface("conv-1", SURFACE, undefined);
|
|
135
|
+
|
|
136
|
+
expect(storedRow()!.updated_at).toBe(marker);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("a changed array overwrites the stored surface", () => {
|
|
140
|
+
const first = recordConversationToolSurface("conv-1", SURFACE);
|
|
141
|
+
const changed = { ...SURFACE, tools: TOOLS.slice(0, 1) };
|
|
142
|
+
|
|
143
|
+
const second = recordConversationToolSurface("conv-1", changed, first);
|
|
144
|
+
|
|
145
|
+
expect(second).not.toBe(first);
|
|
146
|
+
expect(storedRow()?.tools_hash).toBe(second);
|
|
147
|
+
expect(getConversationToolSurface("conv-1")).toEqual(changed);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("a change to the delegation-section state alone overwrites it too", () => {
|
|
151
|
+
// The state is part of the hash: a turn whose scope flips the section
|
|
152
|
+
// (a background run's allowlist after an interactive turn) records it.
|
|
153
|
+
const first = recordConversationToolSurface("conv-1", SURFACE);
|
|
154
|
+
const changed = { ...SURFACE, delegateIndependentTasks: false };
|
|
155
|
+
|
|
156
|
+
const second = recordConversationToolSurface("conv-1", changed, first);
|
|
157
|
+
|
|
158
|
+
expect(second).not.toBe(first);
|
|
159
|
+
expect(storedRow()?.tools_hash).toBe(second);
|
|
160
|
+
expect(getConversationToolSurface("conv-1")).toEqual(changed);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a row recorded before the state existed reads unknown and is replaced on the next record", () => {
|
|
164
|
+
sqlite
|
|
165
|
+
.query(
|
|
166
|
+
/*sql*/ `INSERT INTO conversation_tool_surfaces (conversation_id, tools_json, tools_hash, updated_at) VALUES (?, ?, ?, ?)`,
|
|
167
|
+
)
|
|
168
|
+
.run("conv-1", JSON.stringify(TOOLS), "pre-upgrade-hash", 1);
|
|
169
|
+
|
|
170
|
+
expect(getConversationToolSurface("conv-1")).toEqual({
|
|
171
|
+
tools: TOOLS,
|
|
172
|
+
delegateIndependentTasks: null,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// The stored hash predates the state, so the next live turn's record
|
|
176
|
+
// rewrites the row with it.
|
|
177
|
+
const hash = recordConversationToolSurface("conv-1", SURFACE, undefined);
|
|
178
|
+
|
|
179
|
+
expect(storedRow()?.tools_hash).toBe(hash);
|
|
180
|
+
expect(getConversationToolSurface("conv-1")).toEqual(SURFACE);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("getConversationToolSurface", () => {
|
|
185
|
+
test("returns null when no turn has recorded a surface", () => {
|
|
186
|
+
expect(getConversationToolSurface("conv-1")).toBeNull();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("returns null for an unreadable stored payload", () => {
|
|
190
|
+
sqlite
|
|
191
|
+
.query(
|
|
192
|
+
/*sql*/ `INSERT INTO conversation_tool_surfaces (conversation_id, tools_json, tools_hash, updated_at) VALUES (?, ?, ?, ?)`,
|
|
193
|
+
)
|
|
194
|
+
.run("conv-1", "not json", "h", 1);
|
|
195
|
+
|
|
196
|
+
expect(getConversationToolSurface("conv-1")).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("the row cascades with its conversation", () => {
|
|
200
|
+
recordConversationToolSurface("conv-1", SURFACE);
|
|
201
|
+
|
|
202
|
+
sqlite.query(`DELETE FROM conversations WHERE id = ?`).run("conv-1");
|
|
203
|
+
|
|
204
|
+
expect(getConversationToolSurface("conv-1")).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("plugin facade", () => {
|
|
209
|
+
test("the array accessor is a projection of the recorded surface", async () => {
|
|
210
|
+
expect(await getConversationToolSurfaceViaPluginApi("conv-1")).toBeNull();
|
|
211
|
+
expect(await getRecordedConversationToolSurface("conv-1")).toBeNull();
|
|
212
|
+
|
|
213
|
+
recordConversationToolSurface("conv-1", SURFACE);
|
|
214
|
+
|
|
215
|
+
expect(await getRecordedConversationToolSurface("conv-1")).toEqual(SURFACE);
|
|
216
|
+
// The array shape the public `@vellumai/plugin-api` accessor returns.
|
|
217
|
+
expect(await getConversationToolSurfaceViaPluginApi("conv-1")).toEqual(
|
|
218
|
+
TOOLS,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -85,6 +85,39 @@ describe("GET events/tail", () => {
|
|
|
85
85
|
expect(body.frontier).toBe(5);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
test("returns terminal attachment provenance without rewriting the event", () => {
|
|
89
|
+
stampAndBuffer(
|
|
90
|
+
mkEvent({
|
|
91
|
+
message: {
|
|
92
|
+
type: "message_complete",
|
|
93
|
+
conversationId: CONV,
|
|
94
|
+
messageId: "reply-1",
|
|
95
|
+
attachments: [
|
|
96
|
+
{
|
|
97
|
+
id: "screenshot-1",
|
|
98
|
+
filename: "computer-use-click.png",
|
|
99
|
+
mimeType: "image/png",
|
|
100
|
+
data: "c2NyZWVuc2hvdA==",
|
|
101
|
+
computerUseScreenshot: true,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const body = callTail({ conversationId: CONV, fromSeq: "0" });
|
|
109
|
+
|
|
110
|
+
expect(body.events[0]?.message).toMatchObject({
|
|
111
|
+
type: "message_complete",
|
|
112
|
+
attachments: [
|
|
113
|
+
{
|
|
114
|
+
id: "screenshot-1",
|
|
115
|
+
computerUseScreenshot: true,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
88
121
|
test("an up-to-date anchor yields an empty but complete tail", () => {
|
|
89
122
|
// GIVEN three buffered events (seq 1..3)
|
|
90
123
|
for (let i = 0; i < 3; i++) {
|
|
@@ -643,6 +643,59 @@ describe("repairHistory", () => {
|
|
|
643
643
|
]);
|
|
644
644
|
});
|
|
645
645
|
|
|
646
|
+
test("deferred mixed tail answered by a tool_result plus trailing text gets a synthetic result", () => {
|
|
647
|
+
// Text after the client result closes the assistant turn on the provider
|
|
648
|
+
// side, which then rejects the unanswered search as unpaired. The search
|
|
649
|
+
// is an orphan here, so the synthetic error result keeps the request
|
|
650
|
+
// valid at the cost of that one search. `deepRepairHistory` is the loop's
|
|
651
|
+
// recovery path for that rejection.
|
|
652
|
+
const messages: Message[] = [
|
|
653
|
+
{ role: "user", content: [{ type: "text", text: "Check both" }] },
|
|
654
|
+
{
|
|
655
|
+
role: "assistant",
|
|
656
|
+
content: [
|
|
657
|
+
{ type: "tool_use", id: "tu_a", name: "web_fetch", input: {} },
|
|
658
|
+
{
|
|
659
|
+
type: "server_tool_use",
|
|
660
|
+
id: "stu_deferred",
|
|
661
|
+
name: "web_search",
|
|
662
|
+
input: { query: "news" },
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
role: "user",
|
|
668
|
+
content: [
|
|
669
|
+
{
|
|
670
|
+
type: "tool_result",
|
|
671
|
+
tool_use_id: "tu_a",
|
|
672
|
+
content: "Error: HTTP 404",
|
|
673
|
+
is_error: true,
|
|
674
|
+
},
|
|
675
|
+
{ type: "text", text: "<system_notice>retry</system_notice>" },
|
|
676
|
+
],
|
|
677
|
+
},
|
|
678
|
+
];
|
|
679
|
+
|
|
680
|
+
const { messages: repaired, stats } = deepRepairHistory(messages);
|
|
681
|
+
|
|
682
|
+
expect(stats.missingToolResultsInserted).toBe(1);
|
|
683
|
+
expect(repaired).toHaveLength(3);
|
|
684
|
+
expect(repaired[1].content.map((b) => b.type)).toEqual([
|
|
685
|
+
"tool_use",
|
|
686
|
+
"server_tool_use",
|
|
687
|
+
"web_search_tool_result",
|
|
688
|
+
]);
|
|
689
|
+
expect(repaired[1].content[2]).toMatchObject({
|
|
690
|
+
type: "web_search_tool_result",
|
|
691
|
+
tool_use_id: "stu_deferred",
|
|
692
|
+
});
|
|
693
|
+
expect(repaired[2].content.map((b) => b.type)).toEqual([
|
|
694
|
+
"tool_result",
|
|
695
|
+
"text",
|
|
696
|
+
]);
|
|
697
|
+
});
|
|
698
|
+
|
|
646
699
|
test("cross-message server tool pair from a completed deferred execution survives a reload untouched", () => {
|
|
647
700
|
const messages: Message[] = [
|
|
648
701
|
{ role: "user", content: [{ type: "text", text: "Check both" }] },
|
|
@@ -383,6 +383,32 @@ describe("setInferenceProfileSession", () => {
|
|
|
383
383
|
);
|
|
384
384
|
});
|
|
385
385
|
|
|
386
|
+
test("open with a non-text catalog model — refuses the pin", async () => {
|
|
387
|
+
const conv = createConversation("sess-handler-jev-profile");
|
|
388
|
+
setConfig("llm", {
|
|
389
|
+
profiles: {
|
|
390
|
+
jev: {
|
|
391
|
+
source: "user",
|
|
392
|
+
provider: "jev",
|
|
393
|
+
model: "jev-latest",
|
|
394
|
+
status: "active",
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
try {
|
|
399
|
+
const promise = setInferenceProfileSession({
|
|
400
|
+
conversationId: conv.id,
|
|
401
|
+
profile: "jev",
|
|
402
|
+
});
|
|
403
|
+
await expect(promise).rejects.toThrow(
|
|
404
|
+
/structured answers rather than chat text/,
|
|
405
|
+
);
|
|
406
|
+
expect(getConversation(conv.id)?.inferenceProfile).toBeNull();
|
|
407
|
+
} finally {
|
|
408
|
+
setConfig("llm", {});
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
386
412
|
test("open with an unavailable profile — refuses the pin and names the fix", async () => {
|
|
387
413
|
const conv = createConversation("sess-handler-unavailable-profile");
|
|
388
414
|
managedProxyEnabled = false;
|
|
@@ -14,10 +14,17 @@ import { setConfig } from "./helpers/set-config.js";
|
|
|
14
14
|
// background indexing kicks in.
|
|
15
15
|
setConfig("memory", { enabled: false, v2: { enabled: false } });
|
|
16
16
|
|
|
17
|
+
import {
|
|
18
|
+
linkAttachmentToMessage,
|
|
19
|
+
uploadAttachment,
|
|
20
|
+
} from "../persistence/attachments-store.js";
|
|
17
21
|
import {
|
|
18
22
|
addMessage,
|
|
19
23
|
createConversation,
|
|
24
|
+
getMessageById,
|
|
25
|
+
updateMessageMetadata,
|
|
20
26
|
} from "../persistence/conversation-crud.js";
|
|
27
|
+
import { COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY } from "../persistence/conversation-types.js";
|
|
21
28
|
import { getDb } from "../persistence/db-connection.js";
|
|
22
29
|
import { initializeDb } from "../persistence/db-init.js";
|
|
23
30
|
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
@@ -50,7 +57,15 @@ interface MessagePayload {
|
|
|
50
57
|
toolCalls?: ToolCallPayload[];
|
|
51
58
|
textSegments?: string[];
|
|
52
59
|
contentOrder?: string[];
|
|
53
|
-
contentBlocks?: Array<{
|
|
60
|
+
contentBlocks?: Array<{
|
|
61
|
+
type: string;
|
|
62
|
+
attachment?: { id: string; computerUseScreenshot?: boolean };
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}>;
|
|
65
|
+
attachments?: Array<{
|
|
66
|
+
id: string;
|
|
67
|
+
computerUseScreenshot?: boolean;
|
|
68
|
+
}>;
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
describe("handleListMessages tool_result merging", () => {
|
|
@@ -217,6 +232,75 @@ describe("handleListMessages tool_result merging", () => {
|
|
|
217
232
|
});
|
|
218
233
|
});
|
|
219
234
|
|
|
235
|
+
test("preserves donor automatic screenshot provenance during consolidation", async () => {
|
|
236
|
+
const conv = createConversation();
|
|
237
|
+
await addMessage(
|
|
238
|
+
conv.id,
|
|
239
|
+
"user",
|
|
240
|
+
JSON.stringify([{ type: "text", text: "inspect this" }]),
|
|
241
|
+
);
|
|
242
|
+
const anchor = await addMessage(
|
|
243
|
+
conv.id,
|
|
244
|
+
"assistant",
|
|
245
|
+
JSON.stringify([{ type: "text", text: "Working." }]),
|
|
246
|
+
{ metadata: { anchorContext: "kept" } },
|
|
247
|
+
);
|
|
248
|
+
const explicit = await uploadAttachment(
|
|
249
|
+
"notes.pdf",
|
|
250
|
+
"application/pdf",
|
|
251
|
+
"JVBERg==",
|
|
252
|
+
);
|
|
253
|
+
linkAttachmentToMessage(anchor.id, explicit.id, 0);
|
|
254
|
+
|
|
255
|
+
const donor = await addMessage(
|
|
256
|
+
conv.id,
|
|
257
|
+
"assistant",
|
|
258
|
+
JSON.stringify([{ type: "text", text: "Done." }]),
|
|
259
|
+
);
|
|
260
|
+
const screenshot = await uploadAttachment(
|
|
261
|
+
"computer-use-click.png",
|
|
262
|
+
"image/png",
|
|
263
|
+
"c2NyZWVuc2hvdA==",
|
|
264
|
+
);
|
|
265
|
+
linkAttachmentToMessage(donor.id, screenshot.id, 0);
|
|
266
|
+
updateMessageMetadata(donor.id, {
|
|
267
|
+
[COMPUTER_USE_SCREENSHOT_ATTACHMENT_IDS_KEY]: [screenshot.id],
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const anchorBefore = getMessageById(anchor.id)?.metadata;
|
|
271
|
+
const donorBefore = getMessageById(donor.id)?.metadata;
|
|
272
|
+
const response = await handleListMessages(createTestArgs(conv.id));
|
|
273
|
+
const body = response as { messages: MessagePayload[] };
|
|
274
|
+
const merged = body.messages[1]!;
|
|
275
|
+
|
|
276
|
+
expect(merged.attachments?.map((attachment) => attachment.id)).toEqual([
|
|
277
|
+
explicit.id,
|
|
278
|
+
screenshot.id,
|
|
279
|
+
]);
|
|
280
|
+
expect(merged.attachments?.[0]?.computerUseScreenshot).toBeUndefined();
|
|
281
|
+
expect(merged.attachments?.[1]?.computerUseScreenshot).toBe(true);
|
|
282
|
+
const attachmentBlocks = merged.contentBlocks?.filter(
|
|
283
|
+
(block) => block.type === "attachment",
|
|
284
|
+
);
|
|
285
|
+
expect(
|
|
286
|
+
attachmentBlocks?.map((block) =>
|
|
287
|
+
block.type === "attachment" ? block.attachment?.id : undefined,
|
|
288
|
+
),
|
|
289
|
+
).toEqual([explicit.id, screenshot.id]);
|
|
290
|
+
expect(
|
|
291
|
+
attachmentBlocks?.[0]?.type === "attachment"
|
|
292
|
+
? attachmentBlocks[0].attachment?.computerUseScreenshot
|
|
293
|
+
: undefined,
|
|
294
|
+
).toBeUndefined();
|
|
295
|
+
expect(
|
|
296
|
+
attachmentBlocks?.[1]?.type === "attachment"
|
|
297
|
+
? attachmentBlocks[1].attachment?.computerUseScreenshot
|
|
298
|
+
: undefined,
|
|
299
|
+
).toBe(true);
|
|
300
|
+
expect(getMessageById(anchor.id)?.metadata).toBe(anchorBefore);
|
|
301
|
+
expect(getMessageById(donor.id)?.metadata).toBe(donorBefore);
|
|
302
|
+
});
|
|
303
|
+
|
|
220
304
|
test("orphan tool_result at pagination boundary is suppressed entirely", async () => {
|
|
221
305
|
const conv = createConversation();
|
|
222
306
|
// Orphan tool_result with no preceding assistant in this page. The
|
|
@@ -2,7 +2,9 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
|
+
import { DEFAULT_PROVIDER_CHOICES } from "../config/schemas/llm.js";
|
|
5
6
|
import {
|
|
7
|
+
catalogModelSupportsText,
|
|
6
8
|
getCatalogProviderForModel,
|
|
7
9
|
isModelInCatalog,
|
|
8
10
|
PROVIDER_CATALOG,
|
|
@@ -56,6 +58,7 @@ interface ClientCatalogModel {
|
|
|
56
58
|
supportsCaching?: boolean;
|
|
57
59
|
supportsVision?: boolean;
|
|
58
60
|
supportsToolUse?: boolean;
|
|
61
|
+
supportsText?: boolean;
|
|
59
62
|
pricing?: {
|
|
60
63
|
inputPer1mTokens: number;
|
|
61
64
|
outputPer1mTokens: number;
|
|
@@ -204,6 +207,7 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
204
207
|
expect(clientModel.supportsCaching).toBe(daemonModel.supportsCaching);
|
|
205
208
|
expect(clientModel.supportsVision).toBe(daemonModel.supportsVision);
|
|
206
209
|
expect(clientModel.supportsToolUse).toBe(daemonModel.supportsToolUse);
|
|
210
|
+
expect(clientModel.supportsText).toBe(daemonModel.supportsText);
|
|
207
211
|
expect(clientModel.pricing).toEqual(daemonModel.pricing);
|
|
208
212
|
expect(clientModel.featureFlag).toBe(daemonModel.featureFlag);
|
|
209
213
|
}
|
|
@@ -229,6 +233,16 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
229
233
|
}
|
|
230
234
|
});
|
|
231
235
|
|
|
236
|
+
test("jev-latest opts out of chat text generation", () => {
|
|
237
|
+
expect(catalogModelSupportsText("jev", "jev-latest")).toBe(false);
|
|
238
|
+
expect(catalogModelSupportsText("anthropic", "claude-opus-4-8")).toBe(true);
|
|
239
|
+
expect(catalogModelSupportsText("openai-compatible", "local-model")).toBe(
|
|
240
|
+
true,
|
|
241
|
+
);
|
|
242
|
+
expect(DEFAULT_PROVIDER_CHOICES).not.toContain("jev");
|
|
243
|
+
expect(DEFAULT_PROVIDER_CHOICES).toContain("poolside");
|
|
244
|
+
});
|
|
245
|
+
|
|
232
246
|
test("cache pricing rates are positive when defined", () => {
|
|
233
247
|
for (const entry of PROVIDER_CATALOG) {
|
|
234
248
|
for (const model of entry.models) {
|
|
@@ -252,7 +266,9 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
252
266
|
});
|
|
253
267
|
|
|
254
268
|
test("OpenRouter supportsCaching requires cache-read pricing", () => {
|
|
255
|
-
const openrouter = PROVIDER_CATALOG.find(
|
|
269
|
+
const openrouter = PROVIDER_CATALOG.find(
|
|
270
|
+
(entry) => entry.id === "openrouter",
|
|
271
|
+
);
|
|
256
272
|
expect(openrouter).toBeDefined();
|
|
257
273
|
|
|
258
274
|
for (const model of openrouter!.models) {
|
|
@@ -266,7 +282,9 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
266
282
|
});
|
|
267
283
|
|
|
268
284
|
test("OpenRouter cache-read pricing implies supportsCaching except xAI", () => {
|
|
269
|
-
const openrouter = PROVIDER_CATALOG.find(
|
|
285
|
+
const openrouter = PROVIDER_CATALOG.find(
|
|
286
|
+
(entry) => entry.id === "openrouter",
|
|
287
|
+
);
|
|
270
288
|
expect(openrouter).toBeDefined();
|
|
271
289
|
|
|
272
290
|
for (const model of openrouter!.models) {
|
|
@@ -288,7 +306,9 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
288
306
|
});
|
|
289
307
|
|
|
290
308
|
test("OpenRouter catalog drops ids OpenRouter no longer serves", () => {
|
|
291
|
-
const openrouter = PROVIDER_CATALOG.find(
|
|
309
|
+
const openrouter = PROVIDER_CATALOG.find(
|
|
310
|
+
(entry) => entry.id === "openrouter",
|
|
311
|
+
);
|
|
292
312
|
expect(openrouter).toBeDefined();
|
|
293
313
|
const ids = new Set(openrouter!.models.map((model) => model.id));
|
|
294
314
|
expect(ids.has("deepseek/deepseek-v3.2-speciale")).toBe(false);
|
|
@@ -554,7 +574,10 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
554
574
|
|
|
555
575
|
test("forced tool choice with thinking is scoped to OpenRouter Kimi K2.6", () => {
|
|
556
576
|
expect(
|
|
557
|
-
supportsForcedToolChoiceWithThinking(
|
|
577
|
+
supportsForcedToolChoiceWithThinking(
|
|
578
|
+
"openrouter",
|
|
579
|
+
"moonshotai/kimi-k2.6",
|
|
580
|
+
),
|
|
558
581
|
).toBe(false);
|
|
559
582
|
expect(
|
|
560
583
|
supportsForcedToolChoiceWithThinking(
|