@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292116.41f7caa
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/ARCHITECTURE.md +26 -3
- package/docs/automatic-done.md +32 -0
- package/docs/desktop-browser-cli.md +3 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
- package/node_modules/@vellumai/environments/src/shell.ts +22 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
- package/openapi.yaml +270 -10
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +76 -0
- package/src/__tests__/anthropic-provider.test.ts +2 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
- package/src/__tests__/config-get-vision-flag.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
- package/src/__tests__/conversation-evictor.test.ts +54 -0
- package/src/__tests__/conversation-pairing.test.ts +112 -0
- package/src/__tests__/document-append-idempotency.test.ts +13 -0
- package/src/__tests__/document-create-dedupe.test.ts +13 -0
- package/src/__tests__/document-find-replace.test.ts +13 -0
- package/src/__tests__/document-revision-history.test.ts +390 -0
- package/src/__tests__/document-revision.test.ts +425 -0
- package/src/__tests__/document-tool-security.test.ts +13 -0
- package/src/__tests__/document-update-default-surface.test.ts +13 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
- package/src/__tests__/gateway-flag-listener.test.ts +59 -6
- package/src/__tests__/headless-browser-navigate.test.ts +20 -30
- package/src/__tests__/host-cu-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -2
- package/src/__tests__/identity-routes.test.ts +4 -3
- package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -4
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/notification-platform-adapter.test.ts +83 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/openai-provider.test.ts +38 -11
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/public-ingress-urls.test.ts +26 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
- package/src/__tests__/skill-execute-input.test.ts +11 -0
- package/src/__tests__/tool-approval-handler.test.ts +4 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
- package/src/__tests__/usage-attribution.test.ts +31 -0
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
- package/src/acp/agent-process.ts +6 -1
- package/src/acp/client-handler.ts +6 -1
- package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
- package/src/agent/loop.ts +161 -17
- package/src/api/events/document-editor-show.ts +5 -0
- package/src/api/events/document-editor-update.ts +10 -0
- package/src/api/events/tool-result.ts +1 -0
- package/src/calls/__tests__/call-controller.test.ts +273 -1
- package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
- package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
- package/src/calls/call-constants.ts +10 -0
- package/src/calls/call-controller.ts +359 -25
- package/src/calls/channel-admission-reader.ts +15 -9
- package/src/calls/media-stream-server.ts +18 -0
- package/src/calls/media-stream-stt-session.ts +30 -9
- package/src/calls/twilio-provider.ts +14 -31
- package/src/calls/voice-session-bridge.ts +18 -1
- package/src/cli/commands/browser.ts +1 -2
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
- package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
- package/src/cli/lib/bundled-marketplace.json +54 -2
- package/src/cli/lib/inspect-plugin.ts +15 -15
- package/src/cli/lib/list-installed-plugins.ts +29 -28
- package/src/cli/lib/plugin-artifact.ts +74 -63
- package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
- package/src/cli/lib/plugin-details.ts +27 -42
- package/src/cli/lib/plugin-marketplace.ts +6 -8
- package/src/cli/lib/plugin-surfaces.ts +8 -7
- package/src/cli.ts +1 -3
- package/src/config/__tests__/chat-settings.test.ts +121 -0
- package/src/config/assistant-feature-flags.ts +28 -23
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
- package/src/config/chat-settings.ts +100 -0
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/feature-flag-cache.ts +7 -25
- package/src/config/feature-flag-registry.json +9 -9
- package/src/config/loader.ts +5 -1
- package/src/config/profile-text-generation.ts +25 -21
- package/src/config/schemas/conversations.ts +14 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/notifications.ts +8 -1
- package/src/contacts/gateway-channel-read.ts +1 -1
- package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
- package/src/conversations/__tests__/auto-archive.test.ts +304 -0
- package/src/conversations/auto-archive-activity.ts +61 -0
- package/src/conversations/auto-archive.ts +201 -0
- package/src/daemon/computer-use-observation.test.ts +81 -0
- package/src/daemon/computer-use-observation.ts +23 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
- package/src/daemon/conversation-agent-loop.ts +14 -6
- package/src/daemon/conversation-evictor.ts +50 -16
- package/src/daemon/conversation-messaging.ts +4 -0
- package/src/daemon/conversation-process.ts +15 -0
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/doordash-steps.ts +1 -3
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
- package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
- package/src/daemon/handlers/config-a2a.ts +75 -2
- package/src/daemon/handlers/shared.ts +13 -2
- package/src/daemon/host-cu-proxy.ts +34 -3
- package/src/daemon/lifecycle.ts +15 -2
- package/src/daemon/process-message.ts +5 -0
- package/src/daemon/shutdown-handlers.ts +3 -0
- package/src/daemon/turn-finalization.ts +4 -0
- package/src/desktop/desktop-browser-endpoint.ts +11 -2
- package/src/desktop/desktop-chrome-policy.test.ts +1 -0
- package/src/desktop/desktop-chrome-policy.ts +7 -1
- package/src/desktop/desktop-computer-use.test.ts +14 -4
- package/src/desktop/desktop-session-manager.ts +5 -3
- package/src/documents/document-revisions-store.ts +153 -0
- package/src/documents/document-store.ts +471 -133
- package/src/followups/followup-store.ts +0 -4
- package/src/i18n/__tests__/i18n.test.ts +25 -0
- package/src/i18n/assistant-text.ts +18 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/messages.ts +11 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
- package/src/inbound/oauth-callback-url.ts +30 -1
- package/src/inbound/public-ingress-urls.ts +12 -2
- package/src/ipc/gateway-client.test.ts +40 -0
- package/src/ipc/gateway-client.ts +12 -29
- package/src/ipc/gateway-flag-listener.ts +7 -10
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
- package/src/live-voice/__tests__/protocol.test.ts +77 -0
- package/src/live-voice/__tests__/share-targets.test.ts +163 -0
- package/src/live-voice/live-voice-session.ts +23 -6
- package/src/live-voice/protocol.ts +22 -0
- package/src/live-voice/share-targets.ts +225 -0
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
- package/src/mcp/client.ts +1 -19
- package/src/mcp/mcp-oauth-provider.ts +66 -49
- package/src/media/openrouter-image-service.ts +39 -22
- package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
- package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
- package/src/monitoring/control.ts +2 -1
- package/src/monitoring/oom-inheritance-sweep.ts +96 -0
- package/src/monitoring/oom-kill-reporter.ts +469 -0
- package/src/monitoring/resource-sampler.ts +30 -0
- package/src/notifications/README.md +53 -4
- package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
- package/src/notifications/__tests__/broadcaster.test.ts +558 -6
- package/src/notifications/__tests__/completion-policy.test.ts +37 -0
- package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
- package/src/notifications/adapters/platform.ts +14 -0
- package/src/notifications/assistant-initiated-thread.ts +101 -0
- package/src/notifications/broadcaster.ts +94 -16
- package/src/notifications/chat-reply-policy.ts +11 -0
- package/src/notifications/completion-policy.ts +12 -2
- package/src/notifications/conversation-pairing.ts +15 -70
- package/src/notifications/emit-signal.ts +34 -3
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connect-orchestrator.ts +1 -2
- package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
- package/src/persistence/conversation-auto-archive.ts +170 -0
- package/src/persistence/conversation-crud.ts +2 -1
- package/src/persistence/conversation-queries.ts +15 -8
- package/src/persistence/embeddings/embed.test.ts +96 -0
- package/src/persistence/embeddings/embed.ts +20 -2
- package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
- package/src/persistence/embeddings/embedding-gemini.ts +39 -1
- package/src/persistence/embeddings/embedding-local.ts +6 -0
- package/src/persistence/embeddings/qdrant-manager.ts +5 -0
- package/src/persistence/job-utils.ts +10 -0
- package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
- package/src/persistence/migrations/384-add-document-revision.ts +21 -0
- package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
- package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
- package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
- package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
- package/src/persistence/schema/conversations.ts +1 -0
- package/src/persistence/schema/documents.ts +20 -0
- package/src/persistence/steps.ts +6 -0
- package/src/platform/managed-speech.ts +42 -46
- package/src/plugin-api/vision-support.test.ts +21 -21
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
- package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/fireworks/client.test.ts +168 -0
- package/src/providers/fireworks/client.ts +24 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +141 -84
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +33 -2
- package/src/providers/retry.ts +56 -4
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/types.ts +10 -2
- package/src/providers/unparseable-tool-args.ts +29 -5
- package/src/providers/usage-tracking.ts +5 -3
- package/src/runtime/assistant-event-hub.ts +23 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
- package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +2 -0
- package/src/runtime/channel-retry-sweep.ts +1 -2
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/pending-interactions.ts +20 -2
- package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
- package/src/runtime/routes/contact-routes.ts +2 -2
- package/src/runtime/routes/conversation-list-routes.ts +10 -2
- package/src/runtime/routes/conversation-query-routes.ts +48 -10
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/documents-routes.ts +185 -47
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -0
- package/src/runtime/routes/host-bash-routes.ts +1 -0
- package/src/runtime/routes/host-browser-routes.ts +15 -0
- package/src/runtime/routes/host-cu-routes.ts +4 -1
- package/src/runtime/routes/host-file-routes.ts +1 -0
- package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
- package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
- package/src/runtime/routes/host-transfer-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/integrations/a2a.ts +4 -1
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/notification-routes.ts +1 -2
- package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
- package/src/runtime/routes/user-route-dispatcher.ts +5 -1
- package/src/runtime/verification-templates.ts +1 -2
- package/src/sequence/analytics.ts +0 -4
- package/src/sequence/engine.ts +1 -11
- package/src/sequence/guardrails.ts +1 -6
- package/src/sequence/importer.ts +0 -8
- package/src/sequence/store.ts +0 -10
- package/src/signals/user-message.ts +0 -6
- package/src/skills/catalog-install.ts +52 -23
- package/src/tools/browser/browser-execution.ts +20 -23
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
- package/src/tools/document/document-tool.ts +13 -11
- package/src/tools/executor.ts +1 -2
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/network/__tests__/web-search.test.ts +105 -0
- package/src/tools/network/web-fetch.ts +2 -4
- package/src/tools/network/web-search.ts +239 -12
- package/src/tools/shared/filesystem/format-diff.ts +1 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/skills/execute.ts +10 -7
- package/src/tools/terminal/shell.ts +3 -3
- package/src/tools/tool-approval-handler.ts +9 -1
- package/src/tools/ui-surface/visual-validation.ts +1 -2
- package/src/usage/attribution.ts +18 -1
- package/src/util/__tests__/oom-priority.test.ts +55 -0
- package/src/util/__tests__/worker-memory.test.ts +16 -0
- package/src/util/debounce.ts +0 -4
- package/src/util/oom-priority.ts +79 -0
- package/src/util/platform.ts +0 -2
- package/src/util/plugin-manifest.ts +5 -7
- package/src/util/pricing.ts +25 -0
- package/src/util/worker-memory.ts +9 -3
- package/src/util/worker-process.ts +8 -0
- package/src/workspace/byok-default-profile-ensure.ts +11 -0
- package/src/workspace/hatched-date.ts +11 -4
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -12,6 +12,8 @@ mock.module("../../inbound/platform-callback-registration.js", () => ({
|
|
|
12
12
|
mock.module("../../inbound/public-ingress-urls.js", () => ({
|
|
13
13
|
getOAuthCallbackUrl: () =>
|
|
14
14
|
"https://platform.example/v1/gateway/callbacks/abc/webhooks/oauth/callback/",
|
|
15
|
+
getMcpOAuthClientMetadataUrl: () =>
|
|
16
|
+
"http://localhost/oauth/client-metadata.json",
|
|
15
17
|
}));
|
|
16
18
|
|
|
17
19
|
mock.module("../../config/loader.js", () => ({
|
package/src/mcp/client.ts
CHANGED
|
@@ -22,24 +22,6 @@ const log = getLogger("mcp-client");
|
|
|
22
22
|
|
|
23
23
|
const CONNECT_TIMEOUT_MS = 30_000;
|
|
24
24
|
|
|
25
|
-
// MCP servers occasionally return tools with a missing or non-object
|
|
26
|
-
// `inputSchema` (spec violation, but seen in the wild). Coerce to a valid
|
|
27
|
-
// empty object schema so downstream code that assumes `input_schema: object`
|
|
28
|
-
// (e.g. `injectActivityField`) doesn't crash.
|
|
29
|
-
function normalizeInputSchema(
|
|
30
|
-
raw: unknown,
|
|
31
|
-
toolName: string,
|
|
32
|
-
): Record<string, unknown> {
|
|
33
|
-
if (raw != null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
34
|
-
return raw as Record<string, unknown>;
|
|
35
|
-
}
|
|
36
|
-
log.warn(
|
|
37
|
-
{ toolName, received: typeof raw },
|
|
38
|
-
"MCP tool returned non-object inputSchema; defaulting to empty object schema",
|
|
39
|
-
);
|
|
40
|
-
return { type: "object", properties: {} };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
25
|
/**
|
|
44
26
|
* Behavioral hints a server may attach to a tool (MCP `ToolAnnotations`).
|
|
45
27
|
* Self-reported by the server, so consumers treat them as hints rather than
|
|
@@ -258,7 +240,7 @@ export class McpClient {
|
|
|
258
240
|
return result.tools.map((tool) => ({
|
|
259
241
|
name: tool.name,
|
|
260
242
|
description: tool.description ?? "",
|
|
261
|
-
inputSchema:
|
|
243
|
+
inputSchema: tool.inputSchema,
|
|
262
244
|
annotations: tool.annotations,
|
|
263
245
|
}));
|
|
264
246
|
}
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
OAuthClientMetadata,
|
|
28
28
|
OAuthTokens,
|
|
29
29
|
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
30
|
+
import { buildMcpOAuthClientMetadata } from "@vellumai/service-contracts/mcp-oauth";
|
|
30
31
|
|
|
31
32
|
import { getPlatformAssistantId } from "../config/env.js";
|
|
32
33
|
import { getAssistantName } from "../daemon/identity-helpers.js";
|
|
@@ -49,26 +50,16 @@ import {
|
|
|
49
50
|
const log = getLogger("mcp-oauth");
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
*
|
|
53
|
+
* What stored client information was made against.
|
|
53
54
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*/
|
|
58
|
-
const CLIENT_LOGO_URI = "https://www.vellum.ai/favicon.svg";
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* What a stored client registration was made against.
|
|
62
|
-
*
|
|
63
|
-
* A client identifier belongs to the authorization server that issued it and
|
|
64
|
-
* is registered for one set of redirect URIs, so reusing a registration after
|
|
65
|
-
* either changes is invalid. Recording both is what lets a registration be
|
|
66
|
-
* reused when they still hold, which is the difference between registering
|
|
67
|
-
* once per assistant and registering once per attempt.
|
|
55
|
+
* A client identifier applies to one authorization server and one set of
|
|
56
|
+
* redirect URIs, so reusing it after either changes is invalid. Recording both
|
|
57
|
+
* avoids selecting the client again on every connection attempt.
|
|
68
58
|
*/
|
|
69
59
|
interface ClientRegistrationBinding {
|
|
70
60
|
issuer: string | null;
|
|
71
61
|
redirectUri: string;
|
|
62
|
+
clientMetadataUrl?: string;
|
|
72
63
|
}
|
|
73
64
|
|
|
74
65
|
export interface McpOAuthCallbackResult {
|
|
@@ -96,6 +87,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
96
87
|
private _codeVerifier: string | undefined;
|
|
97
88
|
private _state: string | undefined;
|
|
98
89
|
private _redirectUrl: string | undefined;
|
|
90
|
+
private _clientMetadataUrl: string | undefined;
|
|
99
91
|
private _codePromise: Promise<string> | null = null;
|
|
100
92
|
/** Deferred resolver/rejector for the callback code promise. */
|
|
101
93
|
private _codeResolve: ((code: string) => void) | undefined;
|
|
@@ -127,6 +119,10 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
127
119
|
return this._redirectUrl;
|
|
128
120
|
}
|
|
129
121
|
|
|
122
|
+
get clientMetadataUrl(): string | undefined {
|
|
123
|
+
return this._clientMetadataUrl;
|
|
124
|
+
}
|
|
125
|
+
|
|
130
126
|
// --- clientMetadata ---
|
|
131
127
|
|
|
132
128
|
/**
|
|
@@ -143,23 +139,17 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
143
139
|
* `logo_uri` is the Vellum mark rather than the assistant's own avatar.
|
|
144
140
|
* An authorization server fetching a logo is anonymous, and the avatar is
|
|
145
141
|
* served only behind authentication, so there is no per-assistant URL to
|
|
146
|
-
* give it.
|
|
147
|
-
* one, and the same prerequisite would let this move to Client ID
|
|
148
|
-
* Metadata Documents and drop registration altogether.
|
|
142
|
+
* give it.
|
|
149
143
|
*/
|
|
150
144
|
get clientMetadata(): OAuthClientMetadata {
|
|
151
145
|
const assistantName = getAssistantName();
|
|
152
146
|
const assistantId = getPlatformAssistantId().trim();
|
|
153
|
-
return {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
response_types: ["code"],
|
|
160
|
-
...(assistantId.length > 0 && { software_id: assistantId }),
|
|
161
|
-
software_version: APP_VERSION,
|
|
162
|
-
};
|
|
147
|
+
return buildMcpOAuthClientMetadata({
|
|
148
|
+
clientName: assistantName ?? "Vellum Assistant",
|
|
149
|
+
redirectUris: this._redirectUrl ? [this._redirectUrl] : [],
|
|
150
|
+
softwareId: assistantId.length > 0 ? assistantId : undefined,
|
|
151
|
+
softwareVersion: APP_VERSION,
|
|
152
|
+
});
|
|
163
153
|
}
|
|
164
154
|
|
|
165
155
|
// --- Tokens ---
|
|
@@ -254,15 +244,12 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
254
244
|
// --- Client Information ---
|
|
255
245
|
|
|
256
246
|
/**
|
|
257
|
-
* The stored
|
|
258
|
-
*
|
|
247
|
+
* The stored client information, or `undefined` to make the SDK select a
|
|
248
|
+
* client through CIMD or DCR.
|
|
259
249
|
*
|
|
260
|
-
* Returning the stored value is the normal case
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* the registration provably no longer applies, because the redirect URI
|
|
264
|
-
* it was made for changed or because a different authorization server now
|
|
265
|
-
* fronts the resource.
|
|
250
|
+
* Returning the stored value is the normal case. It is withheld only when
|
|
251
|
+
* the client information provably no longer applies because its redirect
|
|
252
|
+
* URI, metadata URL, or authorization server changed.
|
|
266
253
|
*/
|
|
267
254
|
async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
|
|
268
255
|
const raw = await getSecureKeyAsync(
|
|
@@ -287,7 +274,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
287
274
|
if (stale) {
|
|
288
275
|
log.info(
|
|
289
276
|
{ serverId: this.serverId, reason: stale },
|
|
290
|
-
"Stored client
|
|
277
|
+
"Stored OAuth client information no longer applies; selecting a client again",
|
|
291
278
|
);
|
|
292
279
|
return undefined;
|
|
293
280
|
}
|
|
@@ -309,12 +296,16 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
309
296
|
return;
|
|
310
297
|
}
|
|
311
298
|
|
|
312
|
-
// Record what the
|
|
299
|
+
// Record what the client information applies to, so a later run can tell
|
|
313
300
|
// whether reusing it is still valid.
|
|
314
301
|
if (this._redirectUrl) {
|
|
315
302
|
const binding: ClientRegistrationBinding = {
|
|
316
303
|
issuer: await this.currentIssuer(),
|
|
317
304
|
redirectUri: this._redirectUrl,
|
|
305
|
+
...(this._clientMetadataUrl &&
|
|
306
|
+
info.client_id === this._clientMetadataUrl && {
|
|
307
|
+
clientMetadataUrl: this._clientMetadataUrl,
|
|
308
|
+
}),
|
|
318
309
|
};
|
|
319
310
|
const boundOk = await setSecureKeyAsync(
|
|
320
311
|
mcpOAuthCredentialKey(this.credentialTarget, "client_binding"),
|
|
@@ -323,7 +314,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
323
314
|
if (!boundOk) {
|
|
324
315
|
log.warn(
|
|
325
316
|
{ serverId: this.serverId },
|
|
326
|
-
"Failed to persist OAuth client binding; the
|
|
317
|
+
"Failed to persist OAuth client binding; the client will be selected again on the next flow",
|
|
327
318
|
);
|
|
328
319
|
}
|
|
329
320
|
}
|
|
@@ -342,14 +333,14 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
342
333
|
}
|
|
343
334
|
|
|
344
335
|
/**
|
|
345
|
-
* Why the stored
|
|
336
|
+
* Why the stored client information cannot be reused, or null when it can.
|
|
346
337
|
*
|
|
347
338
|
* The redirect URI is only checked while a flow is being prepared:
|
|
348
339
|
* outside one there is no redirect URI to compare against, and a silent
|
|
349
|
-
* reconnect must not
|
|
340
|
+
* reconnect must not trigger client selection.
|
|
350
341
|
*
|
|
351
342
|
* The issuer is only checked when discovery has run. An unverifiable
|
|
352
|
-
* issuer keeps the
|
|
343
|
+
* issuer keeps the client information rather than discarding it, since the
|
|
353
344
|
* redirect check already covers the case this plugin actually changes.
|
|
354
345
|
*/
|
|
355
346
|
private async describeStaleBinding(): Promise<string | null> {
|
|
@@ -357,8 +348,8 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
357
348
|
mcpOAuthCredentialKey(this.credentialTarget, "client_binding"),
|
|
358
349
|
);
|
|
359
350
|
if (!raw) {
|
|
360
|
-
//
|
|
361
|
-
//
|
|
351
|
+
// Saved before the binding was recorded. Keep it: discarding every
|
|
352
|
+
// pre-existing client would recreate all of them at once.
|
|
362
353
|
return null;
|
|
363
354
|
}
|
|
364
355
|
|
|
@@ -373,6 +364,25 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
373
364
|
return "redirect URI changed";
|
|
374
365
|
}
|
|
375
366
|
|
|
367
|
+
if (
|
|
368
|
+
this._redirectUrl &&
|
|
369
|
+
binding.clientMetadataUrl &&
|
|
370
|
+
binding.clientMetadataUrl !== this._clientMetadataUrl
|
|
371
|
+
) {
|
|
372
|
+
return "client metadata URL changed";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (this._redirectUrl && binding.clientMetadataUrl) {
|
|
376
|
+
const discovery = await this.discoveryState();
|
|
377
|
+
if (
|
|
378
|
+
discovery &&
|
|
379
|
+
discovery.authorizationServerMetadata
|
|
380
|
+
?.client_id_metadata_document_supported !== true
|
|
381
|
+
) {
|
|
382
|
+
return "authorization server no longer supports client metadata documents";
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
376
386
|
const issuer = await this.currentIssuer();
|
|
377
387
|
if (issuer && binding.issuer && binding.issuer !== issuer) {
|
|
378
388
|
return "authorization server changed";
|
|
@@ -536,9 +546,9 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
536
546
|
"OAuth client information key not found in secure storage (already removed)",
|
|
537
547
|
);
|
|
538
548
|
}
|
|
539
|
-
// The binding describes the
|
|
540
|
-
// with it. Leaving it would let a later
|
|
541
|
-
//
|
|
549
|
+
// The binding describes the client information being dropped, so it
|
|
550
|
+
// goes with it. Leaving it would let a later client inherit the previous
|
|
551
|
+
// one's issuer and redirect URI.
|
|
542
552
|
await deleteSecureKeyAsync(
|
|
543
553
|
mcpOAuthCredentialKey(this.credentialTarget, "client_binding"),
|
|
544
554
|
);
|
|
@@ -580,10 +590,13 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
580
590
|
* the first point at which the SDK-generated `state` is known.
|
|
581
591
|
*/
|
|
582
592
|
async startCallbackServer(): Promise<McpOAuthCallbackResult> {
|
|
583
|
-
const { resolveOauthCallbackUrl } =
|
|
593
|
+
const { resolveMcpOAuthClientMetadataUrl, resolveOauthCallbackUrl } =
|
|
584
594
|
await import("../inbound/oauth-callback-url.js");
|
|
585
595
|
|
|
586
596
|
this._redirectUrl = await resolveOauthCallbackUrl();
|
|
597
|
+
this._clientMetadataUrl = resolveMcpOAuthClientMetadataUrl(
|
|
598
|
+
this._redirectUrl,
|
|
599
|
+
);
|
|
587
600
|
|
|
588
601
|
const codePromise = new Promise<string>((resolve, reject) => {
|
|
589
602
|
this._codeResolve = resolve;
|
|
@@ -598,7 +611,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
598
611
|
void codePromise.catch(() => {});
|
|
599
612
|
|
|
600
613
|
log.info(
|
|
601
|
-
{
|
|
614
|
+
{
|
|
615
|
+
serverId: this.serverId,
|
|
616
|
+
redirectUrl: this._redirectUrl,
|
|
617
|
+
clientMetadataUrl: this._clientMetadataUrl,
|
|
618
|
+
},
|
|
602
619
|
"MCP OAuth callback prepared (awaiting state from auth URL)",
|
|
603
620
|
);
|
|
604
621
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
DEFAULT_OPENROUTER_IMAGE_MODEL,
|
|
3
5
|
qualifyImageModelForOpenRouter,
|
|
@@ -13,6 +15,31 @@ import {
|
|
|
13
15
|
} from "./types.js";
|
|
14
16
|
|
|
15
17
|
const OPENROUTER_IMAGES_URL = "https://openrouter.ai/api/v1/images";
|
|
18
|
+
const OpenRouterImageResponseSchema = z.object({
|
|
19
|
+
data: z
|
|
20
|
+
.array(
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
b64_json: z.string().optional().catch(undefined),
|
|
24
|
+
media_type: z.string().optional().catch(undefined),
|
|
25
|
+
})
|
|
26
|
+
.optional()
|
|
27
|
+
.catch(undefined),
|
|
28
|
+
)
|
|
29
|
+
.optional()
|
|
30
|
+
.catch(undefined),
|
|
31
|
+
error: z
|
|
32
|
+
.union([
|
|
33
|
+
z.string(),
|
|
34
|
+
z.object({
|
|
35
|
+
message: z.string().optional().catch(undefined),
|
|
36
|
+
}),
|
|
37
|
+
])
|
|
38
|
+
.optional()
|
|
39
|
+
.catch(undefined),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
type OpenRouterImageResponse = z.infer<typeof OpenRouterImageResponseSchema>;
|
|
16
43
|
|
|
17
44
|
/**
|
|
18
45
|
* Qualify a bare built-in ID or alias for OpenRouter. Call sites that skip
|
|
@@ -76,21 +103,11 @@ export function mapOpenRouterError(error: unknown): string {
|
|
|
76
103
|
return "An unexpected error occurred during image generation.";
|
|
77
104
|
}
|
|
78
105
|
|
|
79
|
-
function errorMessage(body:
|
|
80
|
-
if (typeof body
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
const error = (body as { error?: unknown }).error;
|
|
84
|
-
if (typeof error === "string") {
|
|
85
|
-
return error;
|
|
86
|
-
}
|
|
87
|
-
if (typeof error === "object" && error !== null) {
|
|
88
|
-
const message = (error as { message?: unknown }).message;
|
|
89
|
-
if (typeof message === "string" && message) {
|
|
90
|
-
return message;
|
|
91
|
-
}
|
|
106
|
+
function errorMessage(body: OpenRouterImageResponse | undefined): string {
|
|
107
|
+
if (typeof body?.error === "string") {
|
|
108
|
+
return body.error;
|
|
92
109
|
}
|
|
93
|
-
return "Request failed";
|
|
110
|
+
return body?.error?.message || "Request failed";
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
export async function generateImageOpenRouter(
|
|
@@ -98,7 +115,9 @@ export async function generateImageOpenRouter(
|
|
|
98
115
|
request: ImageGenerationRequest,
|
|
99
116
|
): Promise<ImageGenerationResult> {
|
|
100
117
|
if (credentials.type !== "direct") {
|
|
101
|
-
throw new Error(
|
|
118
|
+
throw new Error(
|
|
119
|
+
"OpenRouter image generation requires an OpenRouter API key.",
|
|
120
|
+
);
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
const model = resolveOpenRouterModel(request.model);
|
|
@@ -127,12 +146,10 @@ export async function generateImageOpenRouter(
|
|
|
127
146
|
...(request.signal ? { signal: request.signal } : {}),
|
|
128
147
|
});
|
|
129
148
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
| undefined;
|
|
149
|
+
const parsed = OpenRouterImageResponseSchema.safeParse(
|
|
150
|
+
await response.json().catch(() => undefined),
|
|
151
|
+
);
|
|
152
|
+
const body = parsed.success ? parsed.data : undefined;
|
|
136
153
|
|
|
137
154
|
if (!response.ok) {
|
|
138
155
|
throw new OpenRouterImageError(response.status, errorMessage(body));
|
|
@@ -140,7 +157,7 @@ export async function generateImageOpenRouter(
|
|
|
140
157
|
|
|
141
158
|
const images: GeneratedImage[] = [];
|
|
142
159
|
for (const entry of body?.data ?? []) {
|
|
143
|
-
if (!entry
|
|
160
|
+
if (!entry?.b64_json) {
|
|
144
161
|
continue;
|
|
145
162
|
}
|
|
146
163
|
images.push({
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdirSync,
|
|
3
|
+
mkdtempSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} 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 { sweepInheritedOomProtection } from "../oom-inheritance-sweep.js";
|
|
13
|
+
import { stat } from "./proc-fixtures.js";
|
|
14
|
+
|
|
15
|
+
const DAEMON = 100;
|
|
16
|
+
const MONITOR = 200;
|
|
17
|
+
|
|
18
|
+
let procRoot: string;
|
|
19
|
+
|
|
20
|
+
function writeProcess(
|
|
21
|
+
pid: number,
|
|
22
|
+
ppid: number,
|
|
23
|
+
comm: string,
|
|
24
|
+
oomScoreAdj: number | null,
|
|
25
|
+
): void {
|
|
26
|
+
const dir = join(procRoot, String(pid));
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
writeFileSync(join(dir, "stat"), stat(pid, comm, "S", ppid, 100));
|
|
29
|
+
if (oomScoreAdj != null) {
|
|
30
|
+
writeFileSync(join(dir, "oom_score_adj"), `${oomScoreAdj}\n`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readAdj(pid: number): string {
|
|
35
|
+
return readFileSync(join(procRoot, String(pid), "oom_score_adj"), "utf-8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
procRoot = mkdtempSync(join(tmpdir(), "oom-sweep-"));
|
|
40
|
+
writeProcess(1, 0, "init", 0);
|
|
41
|
+
writeProcess(DAEMON, 1, "bun", -700);
|
|
42
|
+
writeProcess(MONITOR, DAEMON, "bun", -500);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
rmSync(procRoot, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("sweepInheritedOomProtection", () => {
|
|
50
|
+
test("resets negative descendants and leaves everything else alone", () => {
|
|
51
|
+
writeProcess(300, DAEMON, "node", -700); // forgot to reset
|
|
52
|
+
writeProcess(400, 300, "python3", -700); // inherited through 300
|
|
53
|
+
writeProcess(500, 1, "sshd", -700); // not under the daemon
|
|
54
|
+
writeProcess(600, DAEMON, "bash", 1000); // already raised
|
|
55
|
+
writeProcess(700, DAEMON, "zombie", null); // no oom_score_adj file
|
|
56
|
+
|
|
57
|
+
const resets = sweepInheritedOomProtection({
|
|
58
|
+
daemonPid: DAEMON,
|
|
59
|
+
keepPids: new Set([MONITOR]),
|
|
60
|
+
procRoot,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(resets.sort((a, b) => a.pid - b.pid)).toEqual([
|
|
64
|
+
{ pid: 300, comm: "node", previous: -700 },
|
|
65
|
+
{ pid: 400, comm: "python3", previous: -700 },
|
|
66
|
+
]);
|
|
67
|
+
expect(readAdj(300)).toBe("0");
|
|
68
|
+
expect(readAdj(400)).toBe("0");
|
|
69
|
+
expect(readAdj(DAEMON)).toBe("-700\n");
|
|
70
|
+
expect(readAdj(MONITOR)).toBe("-500\n");
|
|
71
|
+
expect(readAdj(500)).toBe("-700\n");
|
|
72
|
+
expect(readAdj(600)).toBe("1000\n");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("tolerates a ppid cycle in a malformed table", () => {
|
|
76
|
+
writeProcess(300, 400, "a", -700);
|
|
77
|
+
writeProcess(400, 300, "b", -700);
|
|
78
|
+
expect(
|
|
79
|
+
sweepInheritedOomProtection({
|
|
80
|
+
daemonPid: DAEMON,
|
|
81
|
+
keepPids: new Set([MONITOR]),
|
|
82
|
+
procRoot,
|
|
83
|
+
}),
|
|
84
|
+
).toEqual([]);
|
|
85
|
+
});
|
|
86
|
+
});
|