@jacobbd/relay-ai 0.10.1 → 0.11.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/README.md +2 -2
- package/dist/chunk-JIDIH7DS.js +28 -0
- package/dist/chunk-O2XZFXHG.js +4763 -0
- package/dist/chunk-O2XZFXHG.js.map +1 -0
- package/dist/{chunk-TFMHIGRX.js → chunk-TRM2WGI6.js} +6 -23
- package/dist/chunk-TRM2WGI6.js.map +1 -0
- package/dist/chunk-VXQ7ZVTW.js +152 -0
- package/dist/chunk-VXQ7ZVTW.js.map +1 -0
- package/dist/{chunk-YYI6EJ2X.js → chunk-YD6A3ZB3.js} +2246 -6802
- package/dist/chunk-YD6A3ZB3.js.map +1 -0
- package/dist/cli.js +1203 -1072
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +84 -30
- package/dist/core/index.js.map +1 -1
- package/dist/manual-models-VEBTRQFK.js +12 -0
- package/dist/manual-models-VEBTRQFK.js.map +1 -0
- package/dist/{provider-templates-JWT6U5B7.js → provider-templates-JY3NXZK7.js} +3 -2
- package/dist/provider-templates-JY3NXZK7.js.map +1 -0
- package/dist/ui/public/app.js +153 -2
- package/dist/ui/public/style.css +28 -0
- package/dist/{ui-command-LZWTWTQ6.js → ui-command-WKPJI2CU.js} +118 -30
- package/dist/ui-command-WKPJI2CU.js.map +1 -0
- package/package.json +22 -23
- package/dist/chunk-TFMHIGRX.js.map +0 -1
- package/dist/chunk-YYI6EJ2X.js.map +0 -1
- package/dist/ui-command-LZWTWTQ6.js.map +0 -1
- /package/dist/{provider-templates-JWT6U5B7.js.map → chunk-JIDIH7DS.js.map} +0 -0
|
@@ -1,24 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
2
|
+
import {
|
|
3
|
+
__esm,
|
|
4
|
+
__export
|
|
5
|
+
} from "./chunk-JIDIH7DS.js";
|
|
22
6
|
|
|
23
7
|
// src/provider-templates.ts
|
|
24
8
|
var provider_templates_exports = {};
|
|
@@ -319,7 +303,7 @@ var init_provider_templates = __esm({
|
|
|
319
303
|
id: "venice",
|
|
320
304
|
name: "Venice AI",
|
|
321
305
|
authType: "api",
|
|
322
|
-
npm: "
|
|
306
|
+
npm: "@ai-sdk/openai-compatible",
|
|
323
307
|
defaultBaseUrl: "https://api.venice.ai/api/v1",
|
|
324
308
|
signupUrl: "https://venice.ai/settings/api",
|
|
325
309
|
modelSource: "api-list",
|
|
@@ -470,7 +454,6 @@ var init_provider_templates = __esm({
|
|
|
470
454
|
});
|
|
471
455
|
|
|
472
456
|
export {
|
|
473
|
-
__toCommonJS,
|
|
474
457
|
PROVIDER_TEMPLATES,
|
|
475
458
|
listSupportedTemplates,
|
|
476
459
|
listAddableTemplates,
|
|
@@ -480,4 +463,4 @@ export {
|
|
|
480
463
|
provider_templates_exports,
|
|
481
464
|
init_provider_templates
|
|
482
465
|
};
|
|
483
|
-
//# sourceMappingURL=chunk-
|
|
466
|
+
//# sourceMappingURL=chunk-TRM2WGI6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider-templates.ts"],"sourcesContent":["// src/provider-templates.ts — builtin provider templates for relay-ai providers add\n\nexport type ProviderAuthType = 'api' | 'oauth' | 'none';\nexport type ProviderModelSource = 'api-list' | 'static-seed' | 'manual-only' | 'zen-go-api' | 'cline-recommended';\n\nexport interface ProviderTemplate {\n id: string;\n name: string;\n /** Compact label for constrained model lists. */\n shortName?: string;\n authType: ProviderAuthType;\n /** Supported setup methods; defaults to the template's authType. */\n authMethods?: ProviderAuthType[];\n npm: string;\n defaultBaseUrl?: string;\n modelsPath?: string;\n signupUrl?: string;\n urlPlaceholder?: string;\n urlPrompt?: string;\n accountIdPrompt?: string;\n apiKeyOptional?: boolean;\n anonymousFreeModels?: boolean;\n /** Static headers this provider requires on every request (model listing and runtime). */\n headers?: Record<string, string>;\n modelSource: ProviderModelSource;\n staticModels?: Array<{ id: string; name: string }>;\n supported: boolean;\n addable?: boolean;\n hidden?: boolean;\n unsupportedReason?: string;\n /** True for providers that extract subscription tokens — carries account risk. */\n subscriptionRisk?: boolean;\n}\n\n/** Templates aligned with SDK packages shipped in package.json (API-key providers first). */\nexport const PROVIDER_TEMPLATES: ProviderTemplate[] = [\n {\n id: 'cline-pass',\n name: 'ClinePass',\n authType: 'api',\n authMethods: ['api', 'oauth'],\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.cline.bot/api/v1',\n signupUrl: 'https://app.cline.bot',\n modelSource: 'cline-recommended',\n headers: { 'HTTP-Referer': 'https://cline.bot', 'X-Title': 'Cline' },\n supported: true,\n subscriptionRisk: true,\n },\n {\n id: 'groq',\n name: 'Groq',\n authType: 'api',\n npm: '@ai-sdk/groq',\n defaultBaseUrl: 'https://api.groq.com/openai/v1',\n signupUrl: 'https://console.groq.com/keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'nvidia',\n name: 'Nvidia',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://integrate.api.nvidia.com/v1',\n signupUrl: 'https://build.nvidia.com',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'mistral',\n name: 'Mistral',\n authType: 'api',\n npm: '@ai-sdk/mistral',\n defaultBaseUrl: 'https://api.mistral.ai/v1',\n signupUrl: 'https://console.mistral.ai/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'togetherai',\n name: 'Together AI',\n authType: 'api',\n npm: '@ai-sdk/togetherai',\n defaultBaseUrl: 'https://api.together.xyz/v1',\n signupUrl: 'https://api.together.xyz/settings/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n authType: 'api',\n npm: '@ai-sdk/cerebras',\n defaultBaseUrl: 'https://api.cerebras.ai/v1',\n signupUrl: 'https://cloud.cerebras.ai',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n authType: 'api',\n npm: '@ai-sdk/deepinfra',\n defaultBaseUrl: 'https://api.deepinfra.com/v1/openai',\n signupUrl: 'https://deepinfra.com/dash/api_keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.deepseek.com/v1',\n signupUrl: 'https://platform.deepseek.com',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'zhipu',\n name: 'Zhipu AI (GLM)',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n signupUrl: 'https://open.bigmodel.cn',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'moonshot',\n name: 'Moonshot (Kimi)',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.moonshot.cn/v1',\n signupUrl: 'https://platform.moonshot.cn',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'moonshot-global',\n name: 'Moonshot Global (kimi.ai)',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.moonshot.ai/v1',\n signupUrl: 'https://platform.kimi.ai',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'kimi-code',\n name: 'Kimi Code (Subscription Required)',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.kimi.com/coding/v1',\n modelSource: 'static-seed',\n staticModels: [\n { id: 'kimi-for-coding', name: 'Kimi Code K2.7 (Unified)' }\n ],\n supported: true,\n },\n {\n id: 'xai',\n name: 'xAI',\n authType: 'api',\n npm: '@ai-sdk/xai',\n defaultBaseUrl: 'https://api.x.ai/v1',\n signupUrl: 'https://console.x.ai',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n authType: 'api',\n npm: '@ai-sdk/perplexity',\n defaultBaseUrl: 'https://api.perplexity.ai',\n signupUrl: 'https://www.perplexity.ai/settings/api',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'cohere',\n name: 'Cohere',\n authType: 'api',\n npm: '@ai-sdk/cohere',\n defaultBaseUrl: 'https://api.cohere.com/compatibility/v1',\n signupUrl: 'https://dashboard.cohere.com/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'openai',\n name: 'OpenAI',\n authType: 'api',\n npm: '@ai-sdk/openai',\n defaultBaseUrl: 'https://api.openai.com/v1',\n signupUrl: 'https://platform.openai.com/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'google',\n name: 'Google Gemini',\n authType: 'api',\n npm: '@ai-sdk/google',\n defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',\n signupUrl: 'https://aistudio.google.com/apikey',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'alibaba',\n name: 'Alibaba DashScope (China)',\n authType: 'api',\n npm: '@ai-sdk/alibaba',\n defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n signupUrl: 'https://dashscope.console.aliyun.com/apiKey',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'qwen-cloud-token-plan',\n name: 'Qwen Cloud (Token Plan)',\n shortName: 'Qwen Cloud',\n authType: 'api',\n npm: '@ai-sdk/alibaba',\n defaultBaseUrl: 'https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1',\n signupUrl: 'https://home.qwencloud.com/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'qwen-cloud-payg',\n name: 'Qwen Cloud (Pay-As-You-Go)',\n shortName: 'Qwen Cloud',\n authType: 'api',\n npm: '@ai-sdk/alibaba',\n defaultBaseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',\n signupUrl: 'https://home.qwencloud.com/api-keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n authType: 'api',\n npm: '@openrouter/ai-sdk-provider',\n defaultBaseUrl: 'https://openrouter.ai/api/v1',\n signupUrl: 'https://openrouter.ai/keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'kilo',\n name: 'Kilo Code',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.kilo.ai/api/gateway',\n modelsPath: '/models',\n signupUrl: 'https://app.kilo.ai',\n apiKeyOptional: true,\n anonymousFreeModels: true,\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'ollama',\n name: 'Ollama',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'http://127.0.0.1:11434/v1',\n urlPrompt: 'Ollama API Base URL:',\n apiKeyOptional: true,\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'http://127.0.0.1:1234/v1',\n urlPrompt: 'LM Studio API Base URL:',\n apiKeyOptional: true,\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'venice',\n name: 'Venice AI',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.venice.ai/api/v1',\n signupUrl: 'https://venice.ai/settings/api',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'cloudflare',\n name: 'Cloudflare Workers AI',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1',\n modelsPath: '/models/search?task=Text%20Generation',\n signupUrl: 'https://dash.cloudflare.com',\n accountIdPrompt: 'Cloudflare Account ID:',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n authType: 'api',\n npm: '@ai-sdk/anthropic',\n defaultBaseUrl: 'https://api.anthropic.com',\n signupUrl: 'https://console.anthropic.com/settings/keys',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'bedrock',\n name: 'Amazon Bedrock',\n authType: 'api',\n npm: '@ai-sdk/amazon-bedrock',\n modelSource: 'manual-only',\n supported: false,\n unsupportedReason: 'Not supported yet (requires AWS credentials). Optional: relay-ai providers import if already set up in OpenCode CLI.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n authType: 'api',\n npm: '@ai-sdk/azure',\n modelSource: 'manual-only',\n supported: false,\n unsupportedReason: 'Not supported yet (requires Azure deployment URLs). Optional: relay-ai providers import if already set up in OpenCode CLI.',\n },\n {\n id: 'vertex',\n name: 'Google Vertex AI',\n authType: 'none',\n npm: '@ai-sdk/google-vertex',\n modelSource: 'manual-only',\n supported: false,\n unsupportedReason: 'Uses gcloud Application Default Credentials — use relay-ai server --vertex, not an API key.',\n },\n {\n id: 'opencode-cloud',\n name: 'OpenCode Zen / Go',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n signupUrl: 'https://opencode.ai/auth',\n modelSource: 'zen-go-api',\n supported: true,\n },\n {\n id: 'zen',\n name: 'OpenCode Zen',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n signupUrl: 'https://opencode.ai/auth',\n modelSource: 'zen-go-api',\n supported: true,\n addable: false,\n },\n {\n id: 'go',\n name: 'OpenCode Go',\n authType: 'api',\n npm: '@ai-sdk/openai-compatible',\n signupUrl: 'https://opencode.ai/auth',\n modelSource: 'zen-go-api',\n supported: true,\n addable: false,\n },\n // Subscription OAuth providers — Authorization Code + PKCE (browser redirect)\n // ⚠️ These extract tokens from paid subscriptions. Account risk — see plan docs.\n {\n id: 'claude-code',\n name: 'Claude Code (Anthropic subscription)',\n shortName: 'Claude Code',\n authType: 'oauth',\n npm: '@ai-sdk/anthropic',\n defaultBaseUrl: 'https://api.anthropic.com',\n signupUrl: 'https://claude.ai',\n modelSource: 'api-list',\n supported: true,\n hidden: true,\n subscriptionRisk: true,\n },\n {\n id: 'antigravity',\n name: 'Cloud Code Assist OAuth (Google)',\n // Compact label users actually recognise: every surface that shows this\n // provider calls it Antigravity, and it is also the shortest of the two.\n shortName: 'Antigravity',\n authType: 'oauth',\n npm: '@ai-sdk/openai-compatible',\n signupUrl: 'https://antigravity.google',\n modelSource: 'api-list',\n supported: true,\n hidden: true,\n subscriptionRisk: true,\n },\n // OAuth-gated subscription providers — device code or broker sign-in\n {\n id: 'xai-oauth',\n name: 'xAI Grok (SuperGrok)',\n shortName: 'xAI Grok',\n authType: 'oauth',\n npm: '@ai-sdk/xai',\n signupUrl: 'https://x.ai',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'openai-oauth',\n name: 'OpenAI (ChatGPT)',\n shortName: 'OpenAI',\n authType: 'oauth',\n npm: '@ai-sdk/openai',\n signupUrl: 'https://chatgpt.com',\n modelSource: 'api-list',\n supported: true,\n },\n {\n id: 'github-copilot',\n name: 'GitHub Copilot',\n authType: 'oauth',\n npm: '@ai-sdk/openai-compatible',\n defaultBaseUrl: 'https://api.githubcopilot.com',\n modelsPath: '/models',\n signupUrl: 'https://github.com/features/copilot',\n modelSource: 'api-list',\n headers: { 'Editor-Version': 'vscode/1.85.1' },\n supported: true,\n },\n];\n\nexport function listSupportedTemplates(): ProviderTemplate[] {\n return PROVIDER_TEMPLATES\n .filter(t => t.supported && (t.authMethods ?? [t.authType]).includes('api') && t.addable !== false && !t.hidden)\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Supported templates not yet present in the user's registry. */\nexport function listAddableTemplates(configuredIds: Iterable<string> = []): ProviderTemplate[] {\n const configured = new Set(configuredIds);\n return listSupportedTemplates().filter(t => {\n if (t.id === 'opencode-cloud') {\n return !configured.has('zen') && !configured.has('go');\n }\n return !configured.has(t.id);\n });\n}\n\nexport function listVisibleOAuthTemplates(configuredIds: Iterable<string> = []): ProviderTemplate[] {\n const configured = new Set(configuredIds);\n return PROVIDER_TEMPLATES\n .filter(t => {\n const methods = t.authMethods ?? [t.authType];\n return methods.includes('oauth')\n && !methods.includes('api')\n && t.supported\n && t.addable !== false\n && !t.hidden\n && !configured.has(t.id);\n })\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nexport function getTemplateById(id: string): ProviderTemplate | undefined {\n return PROVIDER_TEMPLATES.find(t => t.id === id);\n}\n\nexport function filterTemplates(templates: ProviderTemplate[], query: string): ProviderTemplate[] {\n const q = query.trim().toLowerCase();\n if (!q) return templates;\n return templates.filter(\n t =>\n t.id.toLowerCase().includes(q) ||\n t.name.toLowerCase().includes(q) ||\n t.npm.toLowerCase().includes(q),\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
contextWindowError,
|
|
4
|
+
createLanguageModel,
|
|
5
|
+
getProviderModels,
|
|
6
|
+
loadRegistry,
|
|
7
|
+
modelIdError,
|
|
8
|
+
resolveProviderCredential,
|
|
9
|
+
saveRegistry,
|
|
10
|
+
supportsManualModels,
|
|
11
|
+
translateRequest,
|
|
12
|
+
validateCustomEndpointUrl
|
|
13
|
+
} from "./chunk-O2XZFXHG.js";
|
|
14
|
+
|
|
15
|
+
// src/registry/validate-manual-model.ts
|
|
16
|
+
import { randomUUID } from "crypto";
|
|
17
|
+
import { streamText } from "ai";
|
|
18
|
+
async function completeStream(model, params, signal) {
|
|
19
|
+
const { subagentRouting: _routing, ...call } = params;
|
|
20
|
+
const result = streamText({ model, ...call, maxRetries: 0, abortSignal: signal, onError: () => {
|
|
21
|
+
} });
|
|
22
|
+
const output = Promise.all([result.text, result.toolCalls, result.finishReason, result.responseMessages]);
|
|
23
|
+
output.catch(() => {
|
|
24
|
+
});
|
|
25
|
+
let error;
|
|
26
|
+
let finished = false;
|
|
27
|
+
for await (const part of result.stream) {
|
|
28
|
+
if (part.type === "error" && error === void 0) error = part.error;
|
|
29
|
+
if (part.type === "finish") finished = true;
|
|
30
|
+
}
|
|
31
|
+
if (error !== void 0) throw error;
|
|
32
|
+
signal.throwIfAborted();
|
|
33
|
+
if (!finished) throw new Error("Validation stream ended before completion. Try again.");
|
|
34
|
+
const [text, calls, finishReason, messages] = await output;
|
|
35
|
+
if (!["stop", "tool-calls"].includes(finishReason)) {
|
|
36
|
+
throw new Error(`Validation did not complete (finish reason: ${finishReason}). Model was not added.`);
|
|
37
|
+
}
|
|
38
|
+
return { text, calls, messages };
|
|
39
|
+
}
|
|
40
|
+
async function validateManualModel(provider, modelId, apiKey) {
|
|
41
|
+
const npm = provider.api.npm;
|
|
42
|
+
const model = await createLanguageModel({
|
|
43
|
+
npm,
|
|
44
|
+
modelId,
|
|
45
|
+
apiKey,
|
|
46
|
+
providerId: provider.id,
|
|
47
|
+
authType: provider.authType,
|
|
48
|
+
baseURL: provider.api.url,
|
|
49
|
+
headers: provider.api.headers
|
|
50
|
+
});
|
|
51
|
+
const signal = AbortSignal.timeout(9e4);
|
|
52
|
+
const params = (messages, tools = false) => {
|
|
53
|
+
const translated = translateRequest({
|
|
54
|
+
model: modelId,
|
|
55
|
+
max_tokens: 1024,
|
|
56
|
+
messages: [{ role: "user", content: "Validation" }],
|
|
57
|
+
...tools ? { tools: [{
|
|
58
|
+
name: "relay_validation",
|
|
59
|
+
description: "Echo a validation value. No external actions.",
|
|
60
|
+
input_schema: { type: "object", properties: { value: { type: "string" } }, required: ["value"], additionalProperties: false }
|
|
61
|
+
}], tool_choice: { type: "auto" } } : {}
|
|
62
|
+
}, npm, { reasoningMetadata: { providerId: provider.id, apiBaseUrl: provider.api.url, upstreamModelId: modelId } });
|
|
63
|
+
return { ...translated, messages };
|
|
64
|
+
};
|
|
65
|
+
const generated = await completeStream(model, params([{ role: "user", content: "Reply with exactly RELAY_OK." }]), signal);
|
|
66
|
+
if (!generated.text.trim()) throw new Error("The model returned no text. Model was not added.");
|
|
67
|
+
const nonce = randomUUID();
|
|
68
|
+
const toolPrompt = [{ role: "user", content: `Call relay_validation once with value "${nonce}". Do not answer in text yet.` }];
|
|
69
|
+
const toolResult = await completeStream(model, params(toolPrompt, true), signal);
|
|
70
|
+
const call = toolResult.calls[0];
|
|
71
|
+
if (toolResult.calls.length !== 1 || call.toolName !== "relay_validation" || !call.input || typeof call.input !== "object" || call.input.value !== nonce || Object.keys(call.input).length !== 1) {
|
|
72
|
+
throw new Error("The model did not return the expected tool call and valid arguments. Model was not added.");
|
|
73
|
+
}
|
|
74
|
+
const receipt = `RELAY_RESULT_${randomUUID()}`;
|
|
75
|
+
const continuation = [
|
|
76
|
+
...toolPrompt,
|
|
77
|
+
...toolResult.messages,
|
|
78
|
+
{ role: "tool", content: [{ type: "tool-result", toolCallId: call.toolCallId, toolName: call.toolName, output: { type: "text", value: receipt } }] },
|
|
79
|
+
{ role: "user", content: "Reply with the exact value returned by the tool. Do not call another tool." }
|
|
80
|
+
];
|
|
81
|
+
const roundTrip = await completeStream(model, params(continuation, true), signal);
|
|
82
|
+
if (!roundTrip.text.includes(receipt) || roundTrip.calls.length) {
|
|
83
|
+
throw new Error("The model could not complete the tool-result round-trip. Model was not added.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/registry/manual-models.ts
|
|
88
|
+
function fingerprint(provider) {
|
|
89
|
+
return JSON.stringify([provider.enabled, provider.authRef, provider.authType, provider.api]);
|
|
90
|
+
}
|
|
91
|
+
async function addManualModel(input) {
|
|
92
|
+
if (!input || typeof input !== "object" || typeof input.providerId !== "string") return { ok: false, error: "Provider ID is required." };
|
|
93
|
+
const invalid = modelIdError(input.modelId) ?? contextWindowError(input.contextWindow);
|
|
94
|
+
if (invalid) return { ok: false, error: invalid };
|
|
95
|
+
if (input.displayName !== void 0 && (typeof input.displayName !== "string" || input.displayName.length > 200 || /[\u0000-\u001f\u007f]/u.test(input.displayName))) {
|
|
96
|
+
return { ok: false, error: "Display name must be at most 200 characters without control characters." };
|
|
97
|
+
}
|
|
98
|
+
const id = input.modelId.trim();
|
|
99
|
+
const registry = loadRegistry();
|
|
100
|
+
const provider = registry.providers.find((p) => p.id === input.providerId);
|
|
101
|
+
if (!provider || !provider.enabled) return { ok: false, error: "Provider not found or disabled." };
|
|
102
|
+
if (!supportsManualModels(provider)) return { ok: false, error: "Manual models are supported for API-key OpenAI-compatible, Anthropic, OpenAI and OpenRouter providers." };
|
|
103
|
+
if (getProviderModels(provider).some((m) => m.id === id)) return { ok: false, error: "This model ID is already in the provider catalog." };
|
|
104
|
+
let key = "";
|
|
105
|
+
try {
|
|
106
|
+
if (provider.api.url) {
|
|
107
|
+
const checked = await validateCustomEndpointUrl(provider.api.url, { allowInsecureLocal: true });
|
|
108
|
+
if (!checked.ok) return { ok: false, error: checked.error ?? "Invalid provider URL." };
|
|
109
|
+
}
|
|
110
|
+
key = await resolveProviderCredential(provider.id, provider.authRef) ?? "";
|
|
111
|
+
if (!key && provider.authType !== "none") return { ok: false, error: "No stored API key. Configure this provider before adding a model." };
|
|
112
|
+
const before = fingerprint(provider);
|
|
113
|
+
await validateManualModel(provider, id, key);
|
|
114
|
+
if ((await resolveProviderCredential(provider.id, provider.authRef) ?? "") !== key) {
|
|
115
|
+
return { ok: false, error: "Provider credential changed during validation. Test again." };
|
|
116
|
+
}
|
|
117
|
+
const latest = loadRegistry();
|
|
118
|
+
const current = latest.providers.find((p) => p.id === provider.id);
|
|
119
|
+
if (!current || fingerprint(current) !== before) return { ok: false, error: "Provider settings changed during validation. Test again." };
|
|
120
|
+
if (getProviderModels(current).some((m) => m.id === id)) return { ok: false, error: "This model was added while validation was running." };
|
|
121
|
+
const model = {
|
|
122
|
+
id,
|
|
123
|
+
name: input.displayName?.trim() || id,
|
|
124
|
+
upstreamModelId: id,
|
|
125
|
+
modelFormat: provider.api.npm === "@ai-sdk/anthropic" ? "anthropic" : "openai",
|
|
126
|
+
source: "manual",
|
|
127
|
+
validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
128
|
+
...input.contextWindow === void 0 ? {} : { contextWindow: input.contextWindow, contextWindowSource: "user" }
|
|
129
|
+
};
|
|
130
|
+
current.manualModels = [...current.manualModels ?? [], model];
|
|
131
|
+
saveRegistry(latest);
|
|
132
|
+
return { ok: true, model };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
for (const secret of [key, ...Object.values(provider.api.headers ?? {})].filter(Boolean)) message = message.split(secret).join("[REDACTED]");
|
|
136
|
+
return { ok: false, error: message.slice(0, 1500) };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function removeManualModel(providerId, modelId) {
|
|
140
|
+
const registry = loadRegistry();
|
|
141
|
+
const provider = registry.providers.find((p) => p.id === providerId);
|
|
142
|
+
if (!provider?.manualModels?.some((m) => m.id === modelId)) return { ok: false, error: "Manual model not found." };
|
|
143
|
+
provider.manualModels = provider.manualModels.filter((m) => m.id !== modelId);
|
|
144
|
+
saveRegistry(registry);
|
|
145
|
+
return { ok: true };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
addManualModel,
|
|
150
|
+
removeManualModel
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=chunk-VXQ7ZVTW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry/validate-manual-model.ts","../src/registry/manual-models.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { streamText, type LanguageModel, type ModelMessage } from 'ai';\nimport { createLanguageModel } from '../provider-factory.js';\nimport { translateRequest, type SdkCallParams } from '../sdk-adapter.js';\nimport type { RegistryProvider } from './types.js';\n\n/** Consume every event: HTTP 200 and a partial answer do not establish success. */\nasync function completeStream(model: LanguageModel, params: SdkCallParams, signal: AbortSignal) {\n const { subagentRouting: _routing, ...call } = params;\n const result = streamText({ model, ...call, maxRetries: 0, abortSignal: signal, onError: () => {} } as Parameters<typeof streamText>[0]);\n // Register handlers immediately for SDK promises; stream errors remain authoritative.\n const output = Promise.all([result.text, result.toolCalls, result.finishReason, result.responseMessages]);\n output.catch(() => {});\n let error: unknown;\n let finished = false;\n for await (const part of result.stream) {\n if (part.type === 'error' && error === undefined) error = part.error;\n if (part.type === 'finish') finished = true;\n }\n if (error !== undefined) throw error;\n signal.throwIfAborted();\n if (!finished) throw new Error('Validation stream ended before completion. Try again.');\n const [text, calls, finishReason, messages] = await output;\n if (!['stop', 'tool-calls'].includes(finishReason)) {\n throw new Error(`Validation did not complete (finish reason: ${finishReason}). Model was not added.`);\n }\n return { text, calls, messages };\n}\n\n/** Three small requests; the only tool is a synthetic echo and never executes code. */\nexport async function validateManualModel(provider: RegistryProvider, modelId: string, apiKey: string): Promise<void> {\n const npm = provider.api.npm!;\n const model = await createLanguageModel({ npm, modelId, apiKey, providerId: provider.id,\n authType: provider.authType, baseURL: provider.api.url, headers: provider.api.headers });\n const signal = AbortSignal.timeout(90_000);\n const params = (messages: ModelMessage[], tools = false): SdkCallParams => {\n const translated = translateRequest({ model: modelId, max_tokens: 1024,\n messages: [{ role: 'user', content: 'Validation' }],\n ...(tools ? { tools: [{ name: 'relay_validation', description: 'Echo a validation value. No external actions.',\n input_schema: { type: 'object', properties: { value: { type: 'string' } }, required: ['value'], additionalProperties: false } }], tool_choice: { type: 'auto' } } : {}),\n }, npm, { reasoningMetadata: { providerId: provider.id, apiBaseUrl: provider.api.url, upstreamModelId: modelId } });\n return { ...translated, messages };\n };\n const generated = await completeStream(model, params([{ role: 'user', content: 'Reply with exactly RELAY_OK.' }]), signal);\n if (!generated.text.trim()) throw new Error('The model returned no text. Model was not added.');\n\n const nonce = randomUUID();\n const toolPrompt: ModelMessage[] = [{ role: 'user', content: `Call relay_validation once with value \"${nonce}\". Do not answer in text yet.` }];\n const toolResult = await completeStream(model, params(toolPrompt, true), signal);\n const call = toolResult.calls[0];\n if (toolResult.calls.length !== 1 || call.toolName !== 'relay_validation'\n || !call.input || typeof call.input !== 'object'\n || (call.input as Record<string, unknown>).value !== nonce\n || Object.keys(call.input).length !== 1) {\n throw new Error('The model did not return the expected tool call and valid arguments. Model was not added.');\n }\n const receipt = `RELAY_RESULT_${randomUUID()}`;\n const continuation: ModelMessage[] = [...toolPrompt, ...toolResult.messages,\n { role: 'tool', content: [{ type: 'tool-result', toolCallId: call.toolCallId, toolName: call.toolName, output: { type: 'text', value: receipt } }] },\n { role: 'user', content: 'Reply with the exact value returned by the tool. Do not call another tool.' },\n ];\n const roundTrip = await completeStream(model, params(continuation, true), signal);\n if (!roundTrip.text.includes(receipt) || roundTrip.calls.length) {\n throw new Error('The model could not complete the tool-result round-trip. Model was not added.');\n }\n}\n","import { resolveProviderCredential } from '../env.js';\nimport { loadRegistry, saveRegistry } from './io.js';\nimport { contextWindowError, getProviderModels, modelIdError, supportsManualModels } from './provider-models.js';\nimport { validateManualModel } from './validate-manual-model.js';\nimport { validateCustomEndpointUrl } from './url-security.js';\nimport type { ManualModel, RegistryProvider } from './types.js';\n\nexport interface AddManualModelInput {\n providerId: string;\n modelId: string;\n displayName?: string;\n contextWindow?: number;\n}\nexport interface ManualModelResult { ok: boolean; error?: string; model?: ManualModel }\n\nfunction fingerprint(provider: RegistryProvider): string {\n return JSON.stringify([provider.enabled, provider.authRef, provider.authType, provider.api]);\n}\n\nexport async function addManualModel(input: AddManualModelInput): Promise<ManualModelResult> {\n if (!input || typeof input !== 'object' || typeof input.providerId !== 'string') return { ok: false, error: 'Provider ID is required.' };\n const invalid = modelIdError(input.modelId) ?? contextWindowError(input.contextWindow);\n if (invalid) return { ok: false, error: invalid };\n if (input.displayName !== undefined && (typeof input.displayName !== 'string' || input.displayName.length > 200 || /[\\u0000-\\u001f\\u007f]/u.test(input.displayName))) {\n return { ok: false, error: 'Display name must be at most 200 characters without control characters.' };\n }\n const id = input.modelId.trim();\n const registry = loadRegistry();\n const provider = registry.providers.find(p => p.id === input.providerId);\n if (!provider || !provider.enabled) return { ok: false, error: 'Provider not found or disabled.' };\n if (!supportsManualModels(provider)) return { ok: false, error: 'Manual models are supported for API-key OpenAI-compatible, Anthropic, OpenAI and OpenRouter providers.' };\n if (getProviderModels(provider).some(m => m.id === id)) return { ok: false, error: 'This model ID is already in the provider catalog.' };\n let key = '';\n try {\n if (provider.api.url) {\n const checked = await validateCustomEndpointUrl(provider.api.url, { allowInsecureLocal: true });\n if (!checked.ok) return { ok: false, error: checked.error ?? 'Invalid provider URL.' };\n }\n key = (await resolveProviderCredential(provider.id, provider.authRef)) ?? '';\n if (!key && provider.authType !== 'none') return { ok: false, error: 'No stored API key. Configure this provider before adding a model.' };\n const before = fingerprint(provider);\n await validateManualModel(provider, id, key);\n if (((await resolveProviderCredential(provider.id, provider.authRef)) ?? '') !== key) {\n return { ok: false, error: 'Provider credential changed during validation. Test again.' };\n }\n // Network tests can take time: never write a stale registry over another operation.\n const latest = loadRegistry();\n const current = latest.providers.find(p => p.id === provider.id);\n if (!current || fingerprint(current) !== before) return { ok: false, error: 'Provider settings changed during validation. Test again.' };\n if (getProviderModels(current).some(m => m.id === id)) return { ok: false, error: 'This model was added while validation was running.' };\n const model: ManualModel = {\n id, name: input.displayName?.trim() || id, upstreamModelId: id,\n modelFormat: provider.api.npm === '@ai-sdk/anthropic' ? 'anthropic' : 'openai',\n source: 'manual', validatedAt: new Date().toISOString(),\n ...(input.contextWindow === undefined ? {} : { contextWindow: input.contextWindow, contextWindowSource: 'user' }),\n };\n current.manualModels = [...(current.manualModels ?? []), model];\n saveRegistry(latest);\n return { ok: true, model };\n } catch (error) {\n let message = error instanceof Error ? error.message : String(error);\n for (const secret of [key, ...Object.values(provider.api.headers ?? {})].filter(Boolean)) message = message.split(secret).join('[REDACTED]');\n return { ok: false, error: message.slice(0, 1500) };\n }\n}\n\nexport function removeManualModel(providerId: string, modelId: string): ManualModelResult {\n const registry = loadRegistry();\n const provider = registry.providers.find(p => p.id === providerId);\n if (!provider?.manualModels?.some(m => m.id === modelId)) return { ok: false, error: 'Manual model not found.' };\n provider.manualModels = provider.manualModels.filter(m => m.id !== modelId);\n saveRegistry(registry);\n return { ok: true };\n}\n"],"mappings":"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","names":[]}
|