@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,518 @@
|
|
|
1
|
+
import { isAbortReason } from "../../util/abort-reasons.js";
|
|
2
|
+
import { ProviderError, type ProviderErrorReason } from "../../util/errors.js";
|
|
3
|
+
import { getLogger } from "../../util/logger.js";
|
|
4
|
+
import { safeStringSlice } from "../../util/unicode.js";
|
|
5
|
+
import { fileBlockToProviderText } from "../file-block-text.js";
|
|
6
|
+
import { recordProviderRequestDiagnostics } from "../request-diagnostics.js";
|
|
7
|
+
import { createStreamTimeout } from "../stream-timeout.js";
|
|
8
|
+
import type {
|
|
9
|
+
ContentBlock,
|
|
10
|
+
Message,
|
|
11
|
+
Provider,
|
|
12
|
+
ProviderResponse,
|
|
13
|
+
SendMessageOptions,
|
|
14
|
+
} from "../types.js";
|
|
15
|
+
|
|
16
|
+
const log = getLogger("jev-client");
|
|
17
|
+
|
|
18
|
+
export const JEV_PROVIDER_ID = "jev";
|
|
19
|
+
export const DEFAULT_JEV_BASE_URL = "https://api.typesafe.ai";
|
|
20
|
+
export const DEFAULT_JEV_MODEL = "jev-latest";
|
|
21
|
+
|
|
22
|
+
const SYSTEM_ONE_PATH = "/v1/systemone";
|
|
23
|
+
const MODELS_PATH = "/v1/models";
|
|
24
|
+
const VALIDATION_TIMEOUT_MS = 10_000;
|
|
25
|
+
/** TypeSafe's published request budget is about 32k tokens / 150k English characters. */
|
|
26
|
+
const MAX_STATE_CHARS = 140_000;
|
|
27
|
+
|
|
28
|
+
export interface JevProviderOptions {
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
streamTimeoutMs?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ApiKeyValidationResult =
|
|
34
|
+
{ valid: true } | { valid: false; reason: string };
|
|
35
|
+
|
|
36
|
+
export type JevJsonValue =
|
|
37
|
+
| string
|
|
38
|
+
| number
|
|
39
|
+
| boolean
|
|
40
|
+
| null
|
|
41
|
+
| JevJsonValue[]
|
|
42
|
+
| { [key: string]: JevJsonValue };
|
|
43
|
+
|
|
44
|
+
export type JevQuestion = {
|
|
45
|
+
type: "choice" | "score" | "noul";
|
|
46
|
+
instructions: string;
|
|
47
|
+
criteria?: JevJsonValue;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type JevQuestions = Record<string, JevQuestion>;
|
|
51
|
+
|
|
52
|
+
export interface JevSystemOneRequest {
|
|
53
|
+
state: JevJsonValue;
|
|
54
|
+
model: string;
|
|
55
|
+
questions: JevQuestions;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface JevSystemOneResult {
|
|
59
|
+
model: string;
|
|
60
|
+
answers: Record<string, JevJsonValue>;
|
|
61
|
+
usage?: {
|
|
62
|
+
input_tokens?: number;
|
|
63
|
+
output_tokens?: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DEFAULT_EXPERIMENT_QUESTIONS: JevQuestions = {
|
|
68
|
+
actionable: {
|
|
69
|
+
type: "noul",
|
|
70
|
+
instructions:
|
|
71
|
+
"Does the latest user message ask the assistant to take a specific action?",
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function trimTrailingSlash(url: string): string {
|
|
76
|
+
return url.replace(/\/+$/, "");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveBaseUrl(baseURL?: string): string {
|
|
80
|
+
const trimmed = baseURL?.trim();
|
|
81
|
+
return trimTrailingSlash(
|
|
82
|
+
trimmed && trimmed.length > 0 ? trimmed : DEFAULT_JEV_BASE_URL,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
87
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isJevQuestion(value: unknown): value is JevQuestion {
|
|
91
|
+
if (!isRecord(value)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const type = value.type;
|
|
95
|
+
if (type !== "choice" && type !== "score" && type !== "noul") {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (
|
|
99
|
+
typeof value.instructions !== "string" ||
|
|
100
|
+
value.instructions.trim().length === 0
|
|
101
|
+
) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isJevQuestions(value: unknown): value is JevQuestions {
|
|
108
|
+
if (!isRecord(value)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const entries = Object.entries(value);
|
|
112
|
+
if (entries.length === 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return entries.every(([, question]) => isJevQuestion(question));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* If the last user message is a TypeSafe System One payload, use it as the
|
|
120
|
+
* evaluation request. `state` is optional; callers fall back to the
|
|
121
|
+
* conversation when it is omitted.
|
|
122
|
+
*/
|
|
123
|
+
export function parseSystemOneOverride(
|
|
124
|
+
text: string,
|
|
125
|
+
): { state?: JevJsonValue; questions: JevQuestions } | null {
|
|
126
|
+
const trimmed = text.trim();
|
|
127
|
+
if (!trimmed.startsWith("{")) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
let parsed: unknown;
|
|
131
|
+
try {
|
|
132
|
+
parsed = JSON.parse(trimmed) as unknown;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (!isRecord(parsed) || !isJevQuestions(parsed.questions)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
questions: parsed.questions,
|
|
141
|
+
...(parsed.state !== undefined
|
|
142
|
+
? { state: parsed.state as JevJsonValue }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function contentBlockToText(block: ContentBlock): string {
|
|
148
|
+
switch (block.type) {
|
|
149
|
+
case "text":
|
|
150
|
+
return block.text;
|
|
151
|
+
case "file":
|
|
152
|
+
return fileBlockToProviderText(block);
|
|
153
|
+
case "image":
|
|
154
|
+
return "[image]";
|
|
155
|
+
case "tool_use":
|
|
156
|
+
return `[tool_use ${block.name} ${JSON.stringify(block.input)}]`;
|
|
157
|
+
case "tool_result":
|
|
158
|
+
return `[tool_result ${block.tool_use_id}${
|
|
159
|
+
block.content ? ` ${block.content}` : ""
|
|
160
|
+
}]`;
|
|
161
|
+
default:
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function messageToText(message: Message): string {
|
|
167
|
+
return message.content
|
|
168
|
+
.map((block) => contentBlockToText(block))
|
|
169
|
+
.filter((part) => part.length > 0)
|
|
170
|
+
.join("\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function conversationToState(
|
|
174
|
+
messages: Message[],
|
|
175
|
+
systemPrompt?: string,
|
|
176
|
+
): string {
|
|
177
|
+
const parts: string[] = [];
|
|
178
|
+
if (systemPrompt && systemPrompt.trim().length > 0) {
|
|
179
|
+
parts.push(`system:\n${systemPrompt}`);
|
|
180
|
+
}
|
|
181
|
+
for (const message of messages) {
|
|
182
|
+
const text = messageToText(message);
|
|
183
|
+
if (text.length === 0) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
parts.push(`${message.role}:\n${text}`);
|
|
187
|
+
}
|
|
188
|
+
return safeStringSlice(parts.join("\n\n"), 0, MAX_STATE_CHARS);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function lastUserText(messages: Message[]): string {
|
|
192
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
193
|
+
const message = messages[i];
|
|
194
|
+
if (message?.role !== "user") {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const text = messageToText(message).trim();
|
|
198
|
+
if (text.length > 0) {
|
|
199
|
+
return text;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function classifyHttpStatus(status: number): ProviderErrorReason {
|
|
206
|
+
if (status === 401 || status === 403) {
|
|
207
|
+
return "invalid_credentials";
|
|
208
|
+
}
|
|
209
|
+
if (status === 404) {
|
|
210
|
+
return "model_not_found";
|
|
211
|
+
}
|
|
212
|
+
if (status === 429) {
|
|
213
|
+
return "rate_limited";
|
|
214
|
+
}
|
|
215
|
+
if (status === 529) {
|
|
216
|
+
return "overloaded";
|
|
217
|
+
}
|
|
218
|
+
if (status >= 500) {
|
|
219
|
+
return "server_error";
|
|
220
|
+
}
|
|
221
|
+
if (status >= 400) {
|
|
222
|
+
return "bad_request";
|
|
223
|
+
}
|
|
224
|
+
return "unknown";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function extractErrorMessage(bodyText: string, status: number): string {
|
|
228
|
+
const trimmed = bodyText.trim();
|
|
229
|
+
if (trimmed.length > 0) {
|
|
230
|
+
try {
|
|
231
|
+
const parsed = JSON.parse(trimmed) as {
|
|
232
|
+
error?: unknown;
|
|
233
|
+
message?: unknown;
|
|
234
|
+
detail?: unknown;
|
|
235
|
+
};
|
|
236
|
+
const nested =
|
|
237
|
+
parsed.error && isRecord(parsed.error)
|
|
238
|
+
? parsed.error.message
|
|
239
|
+
: parsed.error;
|
|
240
|
+
const candidate = nested ?? parsed.message ?? parsed.detail;
|
|
241
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
242
|
+
return candidate.trim();
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
// Fall through to the raw body.
|
|
246
|
+
}
|
|
247
|
+
return safeStringSlice(trimmed, 0, 500);
|
|
248
|
+
}
|
|
249
|
+
return `TypeSafe API error (${status})`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function readResponseText(response: Response): Promise<string> {
|
|
253
|
+
try {
|
|
254
|
+
return await response.text();
|
|
255
|
+
} catch {
|
|
256
|
+
return "";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Validate a TypeSafe API key with a tiny System One request. Definitive
|
|
262
|
+
* auth failures (401/403) reject the key; transient errors allow storage.
|
|
263
|
+
*/
|
|
264
|
+
export async function validateJevApiKey(
|
|
265
|
+
apiKey: string,
|
|
266
|
+
options: { baseURL?: string } = {},
|
|
267
|
+
): Promise<ApiKeyValidationResult> {
|
|
268
|
+
const baseURL = resolveBaseUrl(options.baseURL);
|
|
269
|
+
const controller = new AbortController();
|
|
270
|
+
const timeout = setTimeout(() => {
|
|
271
|
+
controller.abort(new Error("TypeSafe key validation timed out"));
|
|
272
|
+
}, VALIDATION_TIMEOUT_MS);
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(`${baseURL}${MODELS_PATH}`, {
|
|
275
|
+
method: "GET",
|
|
276
|
+
headers: {
|
|
277
|
+
Authorization: `Bearer ${apiKey}`,
|
|
278
|
+
Accept: "application/json",
|
|
279
|
+
},
|
|
280
|
+
signal: controller.signal,
|
|
281
|
+
});
|
|
282
|
+
if (response.status === 401) {
|
|
283
|
+
return { valid: false, reason: "API key is invalid or expired." };
|
|
284
|
+
}
|
|
285
|
+
if (response.status === 403) {
|
|
286
|
+
const body = await readResponseText(response);
|
|
287
|
+
return {
|
|
288
|
+
valid: false,
|
|
289
|
+
reason: extractErrorMessage(body, response.status),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (response.status === 404) {
|
|
293
|
+
return await validateJevApiKeyViaSystemOne(apiKey, baseURL);
|
|
294
|
+
}
|
|
295
|
+
if (response.ok) {
|
|
296
|
+
return { valid: true };
|
|
297
|
+
}
|
|
298
|
+
log.warn(
|
|
299
|
+
{ status: response.status },
|
|
300
|
+
"TypeSafe API returned a transient error during key validation, allowing key",
|
|
301
|
+
);
|
|
302
|
+
return { valid: true };
|
|
303
|
+
} catch (error) {
|
|
304
|
+
log.warn(
|
|
305
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
306
|
+
"Network error during TypeSafe key validation, allowing key",
|
|
307
|
+
);
|
|
308
|
+
return { valid: true };
|
|
309
|
+
} finally {
|
|
310
|
+
clearTimeout(timeout);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function validateJevApiKeyViaSystemOne(
|
|
315
|
+
apiKey: string,
|
|
316
|
+
baseURL: string,
|
|
317
|
+
): Promise<ApiKeyValidationResult> {
|
|
318
|
+
try {
|
|
319
|
+
const response = await fetch(`${baseURL}${SYSTEM_ONE_PATH}`, {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: {
|
|
322
|
+
Authorization: `Bearer ${apiKey}`,
|
|
323
|
+
Accept: "application/json",
|
|
324
|
+
"Content-Type": "application/json",
|
|
325
|
+
},
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
state: "ok",
|
|
328
|
+
model: DEFAULT_JEV_MODEL,
|
|
329
|
+
questions: {
|
|
330
|
+
probe: {
|
|
331
|
+
type: "noul",
|
|
332
|
+
instructions: "Is this a non-empty string?",
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
} satisfies JevSystemOneRequest),
|
|
336
|
+
});
|
|
337
|
+
if (response.status === 401) {
|
|
338
|
+
return { valid: false, reason: "API key is invalid or expired." };
|
|
339
|
+
}
|
|
340
|
+
if (response.status === 403) {
|
|
341
|
+
const body = await readResponseText(response);
|
|
342
|
+
return {
|
|
343
|
+
valid: false,
|
|
344
|
+
reason: extractErrorMessage(body, response.status),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (!response.ok && response.status < 500 && response.status !== 429) {
|
|
348
|
+
const body = await readResponseText(response);
|
|
349
|
+
return {
|
|
350
|
+
valid: false,
|
|
351
|
+
reason: extractErrorMessage(body, response.status),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return { valid: true };
|
|
355
|
+
} catch (error) {
|
|
356
|
+
log.warn(
|
|
357
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
358
|
+
"Network error during TypeSafe System One key validation, allowing key",
|
|
359
|
+
);
|
|
360
|
+
return { valid: true };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export class JevProvider implements Provider {
|
|
365
|
+
public readonly name = JEV_PROVIDER_ID;
|
|
366
|
+
public readonly defaultModel: string;
|
|
367
|
+
private readonly apiKey: string;
|
|
368
|
+
private readonly baseURL: string;
|
|
369
|
+
private readonly streamTimeoutMs: number;
|
|
370
|
+
|
|
371
|
+
constructor(apiKey: string, model: string, options: JevProviderOptions = {}) {
|
|
372
|
+
this.apiKey = apiKey;
|
|
373
|
+
this.defaultModel = model.trim() || DEFAULT_JEV_MODEL;
|
|
374
|
+
this.baseURL = resolveBaseUrl(options.baseURL);
|
|
375
|
+
this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Native TypeSafe System One call. `sendMessage` is a thin adapter over
|
|
380
|
+
* this so existing profiles and probes can reach Jev.
|
|
381
|
+
*/
|
|
382
|
+
async systemOne(
|
|
383
|
+
request: JevSystemOneRequest,
|
|
384
|
+
options: {
|
|
385
|
+
signal?: AbortSignal;
|
|
386
|
+
headers?: Record<string, string>;
|
|
387
|
+
} = {},
|
|
388
|
+
): Promise<JevSystemOneResult> {
|
|
389
|
+
const url = `${this.baseURL}${SYSTEM_ONE_PATH}`;
|
|
390
|
+
recordProviderRequestDiagnostics({ model_id: request.model });
|
|
391
|
+
const { signal, cleanup } = createStreamTimeout(
|
|
392
|
+
this.streamTimeoutMs,
|
|
393
|
+
options.signal,
|
|
394
|
+
);
|
|
395
|
+
try {
|
|
396
|
+
const response = await fetch(url, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: {
|
|
399
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
400
|
+
Accept: "application/json",
|
|
401
|
+
"Content-Type": "application/json",
|
|
402
|
+
...(options.headers ?? {}),
|
|
403
|
+
},
|
|
404
|
+
body: JSON.stringify(request),
|
|
405
|
+
signal,
|
|
406
|
+
});
|
|
407
|
+
const bodyText = await readResponseText(response);
|
|
408
|
+
if (!response.ok) {
|
|
409
|
+
const reason = classifyHttpStatus(response.status);
|
|
410
|
+
throw new ProviderError(
|
|
411
|
+
extractErrorMessage(bodyText, response.status),
|
|
412
|
+
JEV_PROVIDER_ID,
|
|
413
|
+
response.status,
|
|
414
|
+
{ reason, rawBody: bodyText },
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
let parsed: unknown;
|
|
418
|
+
try {
|
|
419
|
+
parsed = JSON.parse(bodyText) as unknown;
|
|
420
|
+
} catch {
|
|
421
|
+
throw new ProviderError(
|
|
422
|
+
"TypeSafe returned a non-JSON response.",
|
|
423
|
+
JEV_PROVIDER_ID,
|
|
424
|
+
response.status,
|
|
425
|
+
{ reason: "bad_request", rawBody: bodyText },
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
if (!isRecord(parsed) || !isRecord(parsed.answers)) {
|
|
429
|
+
throw new ProviderError(
|
|
430
|
+
"TypeSafe returned a response without answers.",
|
|
431
|
+
JEV_PROVIDER_ID,
|
|
432
|
+
response.status,
|
|
433
|
+
{ reason: "bad_request", rawBody: bodyText },
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const usage = isRecord(parsed.usage) ? parsed.usage : undefined;
|
|
437
|
+
return {
|
|
438
|
+
model:
|
|
439
|
+
typeof parsed.model === "string" && parsed.model.length > 0
|
|
440
|
+
? parsed.model
|
|
441
|
+
: request.model,
|
|
442
|
+
answers: parsed.answers as Record<string, JevJsonValue>,
|
|
443
|
+
...(usage
|
|
444
|
+
? {
|
|
445
|
+
usage: {
|
|
446
|
+
input_tokens:
|
|
447
|
+
typeof usage.input_tokens === "number"
|
|
448
|
+
? usage.input_tokens
|
|
449
|
+
: undefined,
|
|
450
|
+
output_tokens:
|
|
451
|
+
typeof usage.output_tokens === "number"
|
|
452
|
+
? usage.output_tokens
|
|
453
|
+
: undefined,
|
|
454
|
+
},
|
|
455
|
+
}
|
|
456
|
+
: {}),
|
|
457
|
+
};
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error instanceof ProviderError) {
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
const abortReason =
|
|
463
|
+
options.signal?.aborted && isAbortReason(options.signal.reason)
|
|
464
|
+
? options.signal.reason
|
|
465
|
+
: undefined;
|
|
466
|
+
throw new ProviderError(
|
|
467
|
+
error instanceof Error ? error.message : String(error),
|
|
468
|
+
JEV_PROVIDER_ID,
|
|
469
|
+
undefined,
|
|
470
|
+
{
|
|
471
|
+
cause: error,
|
|
472
|
+
abortReason,
|
|
473
|
+
reason: abortReason ? undefined : "network_error",
|
|
474
|
+
},
|
|
475
|
+
);
|
|
476
|
+
} finally {
|
|
477
|
+
cleanup();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async sendMessage(
|
|
482
|
+
messages: Message[],
|
|
483
|
+
options?: SendMessageOptions,
|
|
484
|
+
): Promise<ProviderResponse> {
|
|
485
|
+
const { systemPrompt, config, onEvent, signal } = options ?? {};
|
|
486
|
+
const configObj = config as Record<string, unknown> | undefined;
|
|
487
|
+
const modelOverride = configObj?.model as string | undefined;
|
|
488
|
+
const usageAttributionHeaders = configObj?.usageAttributionHeaders as
|
|
489
|
+
Record<string, string> | undefined;
|
|
490
|
+
const activeModel = modelOverride?.trim() || this.defaultModel;
|
|
491
|
+
const override = parseSystemOneOverride(lastUserText(messages));
|
|
492
|
+
const request: JevSystemOneRequest = {
|
|
493
|
+
model: activeModel,
|
|
494
|
+
state: override?.state ?? conversationToState(messages, systemPrompt),
|
|
495
|
+
questions: override?.questions ?? DEFAULT_EXPERIMENT_QUESTIONS,
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const result = await this.systemOne(request, {
|
|
499
|
+
signal,
|
|
500
|
+
headers: usageAttributionHeaders,
|
|
501
|
+
});
|
|
502
|
+
const text = JSON.stringify(result.answers, null, 2);
|
|
503
|
+
onEvent?.({ type: "text_delta", text });
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
content: [{ type: "text", text }],
|
|
507
|
+
model: result.model,
|
|
508
|
+
usage: {
|
|
509
|
+
inputTokens: result.usage?.input_tokens ?? 0,
|
|
510
|
+
outputTokens: result.usage?.output_tokens ?? 0,
|
|
511
|
+
},
|
|
512
|
+
stopReason: "end_turn",
|
|
513
|
+
resolvedEndpoint: `${this.baseURL}${SYSTEM_ONE_PATH}`,
|
|
514
|
+
rawRequest: request,
|
|
515
|
+
rawResponse: result,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { PLATFORM_PROVIDER_META } from "./platform-proxy/constants.js";
|
|
2
2
|
|
|
3
3
|
export type LongContextMode =
|
|
4
|
-
|
|
5
|
-
| "provider-request-option"
|
|
6
|
-
| "unsupported";
|
|
4
|
+
"native-model" | "provider-request-option" | "unsupported";
|
|
7
5
|
|
|
8
6
|
export interface CatalogModelPricingTier {
|
|
9
7
|
/**
|
|
@@ -75,6 +73,13 @@ export interface CatalogModel {
|
|
|
75
73
|
*/
|
|
76
74
|
supportsAudioInput?: boolean;
|
|
77
75
|
supportsToolUse?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the model produces free-form chat text. Omit (or true) for
|
|
78
|
+
* ordinary chat models. False for structured-decision models that return
|
|
79
|
+
* answers rather than generated text; those stay out of conversation
|
|
80
|
+
* pickers and cannot be the conversation model.
|
|
81
|
+
*/
|
|
82
|
+
supportsText?: boolean;
|
|
78
83
|
supportsEffort?: boolean;
|
|
79
84
|
/**
|
|
80
85
|
* Whether this provider/model serving surface accepts a forced OpenAI
|
|
@@ -2526,6 +2531,38 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2526
2531
|
apiKeyUrl: "https://poolside.ai",
|
|
2527
2532
|
apiKeyPlaceholder: "Your Poolside API key",
|
|
2528
2533
|
},
|
|
2534
|
+
{
|
|
2535
|
+
id: "jev",
|
|
2536
|
+
displayName: "Jev",
|
|
2537
|
+
subtitle:
|
|
2538
|
+
"TypeSafe System One decision model. Returns structured answers, not generated text. Requires a TypeSafe API key.",
|
|
2539
|
+
setupMode: "api-key",
|
|
2540
|
+
setupHint: "Enter your TypeSafe API key to enable Jev.",
|
|
2541
|
+
envVar: "TYPESAFE_API_KEY",
|
|
2542
|
+
credentialsGuide: {
|
|
2543
|
+
description: "Sign in to TypeSafe and create an API key.",
|
|
2544
|
+
url: "https://typesafe.ai",
|
|
2545
|
+
linkLabel: "Open TypeSafe",
|
|
2546
|
+
},
|
|
2547
|
+
models: [
|
|
2548
|
+
{
|
|
2549
|
+
id: "jev-latest",
|
|
2550
|
+
displayName: "Jev",
|
|
2551
|
+
// TypeSafe's published request budget is about 32,000 tokens.
|
|
2552
|
+
contextWindowTokens: 32000,
|
|
2553
|
+
maxOutputTokens: 4096,
|
|
2554
|
+
supportsThinking: false,
|
|
2555
|
+
supportsCaching: false,
|
|
2556
|
+
supportsVision: false,
|
|
2557
|
+
supportsToolUse: false,
|
|
2558
|
+
supportsText: false,
|
|
2559
|
+
pricing: { inputPer1mTokens: 0.042, outputPer1mTokens: 0 },
|
|
2560
|
+
},
|
|
2561
|
+
],
|
|
2562
|
+
defaultModel: "jev-latest",
|
|
2563
|
+
apiKeyUrl: "https://typesafe.ai",
|
|
2564
|
+
apiKeyPlaceholder: "Your TypeSafe API key",
|
|
2565
|
+
},
|
|
2529
2566
|
{
|
|
2530
2567
|
id: "vellum",
|
|
2531
2568
|
displayName: "Vellum",
|
|
@@ -2564,6 +2601,24 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] =
|
|
|
2564
2601
|
supportsPlatformAuth: PLATFORM_PROVIDER_META[entry.id]?.managed === true,
|
|
2565
2602
|
}));
|
|
2566
2603
|
|
|
2604
|
+
/**
|
|
2605
|
+
* Whether a catalog model produces free-form chat text. Unlisted providers
|
|
2606
|
+
* and model ids default to true so custom endpoints and unknown snapshots
|
|
2607
|
+
* stay usable as conversation models.
|
|
2608
|
+
*/
|
|
2609
|
+
export function catalogModelSupportsText(
|
|
2610
|
+
provider: string | null | undefined,
|
|
2611
|
+
modelId: string | null | undefined,
|
|
2612
|
+
): boolean {
|
|
2613
|
+
if (typeof provider !== "string" || typeof modelId !== "string") {
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
const model = PROVIDER_CATALOG.find((p) => p.id === provider)?.models.find(
|
|
2617
|
+
(m) => m.id === modelId,
|
|
2618
|
+
);
|
|
2619
|
+
return model?.supportsText !== false;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2567
2622
|
/** Check if a model ID is in the catalog for a given provider. */
|
|
2568
2623
|
export function isModelInCatalog(provider: string, modelId: string): boolean {
|
|
2569
2624
|
const entry = PROVIDER_CATALOG.find((p) => p.id === provider);
|
|
@@ -57,7 +57,8 @@ function contentBlocks(msg: ServerToolPairingMessage): ReadonlyArray<unknown> {
|
|
|
57
57
|
* `tool_use`, no result emitted) and executes it on the next request, placing
|
|
58
58
|
* the web_search_tool_result at the head of the next assistant message. Both
|
|
59
59
|
* the deferred tail and the resulting split pair must be sent back verbatim
|
|
60
|
-
* for the provider to pair and execute them
|
|
60
|
+
* for the provider to pair and execute them, and the user message answering
|
|
61
|
+
* a deferred tail must hold tool_result blocks alone.
|
|
61
62
|
*/
|
|
62
63
|
export interface ServerToolPairing {
|
|
63
64
|
/**
|
|
@@ -67,8 +68,12 @@ export interface ServerToolPairing {
|
|
|
67
68
|
resolvedPairIds: Set<string>;
|
|
68
69
|
/**
|
|
69
70
|
* Unanswered use ids in the final assistant message of an active tool-loop
|
|
70
|
-
* continuation
|
|
71
|
-
*
|
|
71
|
+
* continuation: nothing follows it, or only user messages made solely of
|
|
72
|
+
* tool_result blocks. The provider executes these on this request. Any
|
|
73
|
+
* other trailing block (guidance text after the results, a queued user
|
|
74
|
+
* message) closes the assistant turn on the provider side, which then
|
|
75
|
+
* rejects the unanswered use as unpaired, so such a use is an orphan to
|
|
76
|
+
* repair rather than a deferral to preserve.
|
|
72
77
|
*/
|
|
73
78
|
deferredUseIds: Set<string>;
|
|
74
79
|
/** Ids whose use/result pair spans two messages. */
|
|
@@ -118,10 +123,14 @@ export function analyzeServerToolPairing(
|
|
|
118
123
|
if (lastAssistantIndex >= 0) {
|
|
119
124
|
const trailing = messages.slice(lastAssistantIndex + 1);
|
|
120
125
|
const trailingAllUser = trailing.every((m) => m.role === "user");
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
const trailingBlocks = trailing.flatMap((m) => [...contentBlocks(m)]);
|
|
127
|
+
const trailingIsToolResultsOnly =
|
|
128
|
+
trailingBlocks.length > 0 &&
|
|
129
|
+
trailingBlocks.every(isToolResultShapedBlock);
|
|
130
|
+
if (
|
|
131
|
+
trailingAllUser &&
|
|
132
|
+
(trailing.length === 0 || trailingIsToolResultsOnly)
|
|
133
|
+
) {
|
|
125
134
|
for (const block of contentBlocks(messages[lastAssistantIndex])) {
|
|
126
135
|
if (isServerToolUseBlock(block) && !resolvedPairIds.has(block.id)) {
|
|
127
136
|
deferredUseIds.add(block.id);
|
package/src/runtime/AGENTS.md
CHANGED
|
@@ -301,6 +301,6 @@ The provider-level rate limiter (`providers/ratelimit.ts`) also logs warnings (m
|
|
|
301
301
|
|
|
302
302
|
## HTTP-Only Transport
|
|
303
303
|
|
|
304
|
-
HTTP is the sole transport for
|
|
304
|
+
HTTP is the sole transport for browser and desktop clients; the CLI and the gateway reach the daemon over the IPC socket (see `assistant/AGENTS.md`). The runtime HTTP server (`assistant/src/runtime/http-server.ts`) is the canonical API surface. Clients connect via HTTP for request/response operations and SSE (`GET /v1/events`) for streaming server-to-client events.
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
A skill never calls the runtime HTTP API directly: every request from a skill targets the gateway, and `gateway-only-guard.test.ts` fails CI on a runtime-port URL in a skill. For configuration reads, use the CLI (`assistant config get`); for control-plane writes with no CLI verb, use `$INTERNAL_GATEWAY_BASE_URL`. See "Gateway-Only API Consumption" in `gateway/AGENTS.md` for the rule and its exceptions.
|