@ganju/utils 0.0.8 → 0.0.9
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/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +29 -11
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/llmBaseUrl.d.ts +39 -0
- package/dist/llmBaseUrl.d.ts.map +1 -0
- package/dist/llmBaseUrl.js +65 -0
- package/dist/localizeZodIssue.d.ts.map +1 -1
- package/dist/localizeZodIssue.js +6 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +32 -8
- package/package.json +1 -1
- package/src/constants.ts +33 -11
- package/src/index.ts +8 -0
- package/src/llmBaseUrl.ts +65 -0
- package/src/localizeZodIssue.ts +6 -0
- package/src/schema.ts +63 -11
package/dist/constants.d.ts
CHANGED
|
@@ -362,6 +362,9 @@ export declare const constants: {
|
|
|
362
362
|
model: string;
|
|
363
363
|
label: string;
|
|
364
364
|
}[];
|
|
365
|
+
LLM_CATALOG_CUSTOM_KEY: string;
|
|
366
|
+
LLM_BASE_URL_REQUIRED_MESSAGE: string;
|
|
367
|
+
LLM_BASE_URL_INVALID_MESSAGE: string;
|
|
365
368
|
LLM_SYSTEM_DEFAULT: string;
|
|
366
369
|
MAX_TOOL_LOOPS: number;
|
|
367
370
|
CHANNEL_HISTORY_LIMIT: number;
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AA6JA,QAAA,MAAM,yBAAyB,EAAiB,UAAU,CAAC;AAC3D,QAAA,MAAM,+BAA+B,EAAuB,gBAAgB,CAAC;AAC7E,QAAA,MAAM,8BAA8B,EAAsB,eAAe,CAAC;AAC1E,QAAA,MAAM,wBAAwB,EAAgB,SAAS,CAAC;AAuCxD,QAAA,MAAM,sBAAsB,EAAiB,UAAU,CAAC;AACxD,QAAA,MAAM,4BAA4B,EAAuB,gBAAgB,CAAC;AAC1E,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAClD,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAmYlD,QAAA,MAAM,sBAAsB,EAAkB,WAAW,CAAC;AAC1D,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;AAGjD,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.js
CHANGED
|
@@ -496,8 +496,8 @@ const DEFAULT_LLM_SYSTEM_PROMPT = 'You are a helpful assistant. Answer the user
|
|
|
496
496
|
const LLM_CATALOG = [
|
|
497
497
|
{
|
|
498
498
|
provider: LLM_PROVIDER_GOOGLE,
|
|
499
|
-
model: 'gemini-3-flash
|
|
500
|
-
label: 'Gemini 3 Flash'
|
|
499
|
+
model: 'gemini-3.8-flash',
|
|
500
|
+
label: 'Gemini 3.8 Flash'
|
|
501
501
|
},
|
|
502
502
|
{
|
|
503
503
|
provider: LLM_PROVIDER_GOOGLE,
|
|
@@ -506,13 +506,13 @@ const LLM_CATALOG = [
|
|
|
506
506
|
},
|
|
507
507
|
{
|
|
508
508
|
provider: LLM_PROVIDER_OPENAI,
|
|
509
|
-
model: 'gpt-
|
|
510
|
-
label: 'GPT-
|
|
509
|
+
model: 'gpt-5.4-mini',
|
|
510
|
+
label: 'GPT-5.4 mini'
|
|
511
511
|
},
|
|
512
512
|
{
|
|
513
513
|
provider: LLM_PROVIDER_OPENAI,
|
|
514
|
-
model: 'gpt-
|
|
515
|
-
label: 'GPT-
|
|
514
|
+
model: 'gpt-5.5',
|
|
515
|
+
label: 'GPT-5.5'
|
|
516
516
|
},
|
|
517
517
|
{
|
|
518
518
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
@@ -521,15 +521,30 @@ const LLM_CATALOG = [
|
|
|
521
521
|
},
|
|
522
522
|
{
|
|
523
523
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
524
|
-
model: 'claude-sonnet-
|
|
525
|
-
label: 'Claude Sonnet
|
|
524
|
+
model: 'claude-sonnet-5',
|
|
525
|
+
label: 'Claude Sonnet 5'
|
|
526
526
|
},
|
|
527
527
|
{
|
|
528
528
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
529
|
-
model: 'claude-opus-
|
|
530
|
-
label: 'Claude
|
|
529
|
+
model: 'claude-opus-5',
|
|
530
|
+
label: 'Claude Opus 5'
|
|
531
531
|
}
|
|
532
532
|
];
|
|
533
|
+
// The picker's escape hatch. Anything that speaks the OpenAI chat-completions
|
|
534
|
+
// protocol — a gateway, an aggregator, a self-hosted server — is reachable
|
|
535
|
+
// without a catalog row: the customer supplies the model id and the base URL,
|
|
536
|
+
// and the request goes out through the OpenAI adapter. Not a provider value, so
|
|
537
|
+
// it can never reach the database; the dashboard translates it into
|
|
538
|
+
// LLM_PROVIDER_OPENAI_COMPATIBLE on submit.
|
|
539
|
+
const LLM_CATALOG_CUSTOM_KEY = 'custom';
|
|
540
|
+
// Validation messages for an organization LLM's base URL. Constants rather than
|
|
541
|
+
// inline strings because localizeZodIssue keys its translations on the exact
|
|
542
|
+
// English text, so the two files have to name the same value. Both also carry a
|
|
543
|
+
// word matchStatus recognises ("required", "must be"): the update path re-checks
|
|
544
|
+
// the merged row and throws the message as a plain Error, and anything
|
|
545
|
+
// unrecognised there becomes an opaque 500 instead of a 400.
|
|
546
|
+
const LLM_BASE_URL_REQUIRED_MESSAGE = 'A base URL is required for an OpenAI-compatible model';
|
|
547
|
+
const LLM_BASE_URL_INVALID_MESSAGE = 'Base URL must be a public https:// address';
|
|
533
548
|
const LLM_SYSTEM_DEFAULT = 'SYSTEM_DEFAULT';
|
|
534
549
|
const MAX_TOOL_LOOPS = 8;
|
|
535
550
|
// How many recent messages a channel turn replays as context.
|
|
@@ -1783,7 +1798,7 @@ const POLAR_SERVER_PRODUCTION = 'production';
|
|
|
1783
1798
|
const CONSENT_DOCUMENT_TERMS = 'terms';
|
|
1784
1799
|
const CONSENT_DOCUMENT_PRIVACY = 'privacy';
|
|
1785
1800
|
const CONSENT_DOCUMENTS = [CONSENT_DOCUMENT_TERMS, CONSENT_DOCUMENT_PRIVACY];
|
|
1786
|
-
const CONSENT_CURRENT_VERSION = '2026-
|
|
1801
|
+
const CONSENT_CURRENT_VERSION = '2026-09-10';
|
|
1787
1802
|
const CONSENT_SOURCE_SIGNUP = 'signup';
|
|
1788
1803
|
const CONSENT_SOURCE_REACCEPT = 'reaccept';
|
|
1789
1804
|
const CONSENT_SOURCES = [CONSENT_SOURCE_SIGNUP, CONSENT_SOURCE_REACCEPT];
|
|
@@ -2151,6 +2166,9 @@ exports.constants = {
|
|
|
2151
2166
|
DEFAULT_LLM_MODEL,
|
|
2152
2167
|
DEFAULT_LLM_SYSTEM_PROMPT,
|
|
2153
2168
|
LLM_CATALOG,
|
|
2169
|
+
LLM_CATALOG_CUSTOM_KEY,
|
|
2170
|
+
LLM_BASE_URL_REQUIRED_MESSAGE,
|
|
2171
|
+
LLM_BASE_URL_INVALID_MESSAGE,
|
|
2154
2172
|
LLM_SYSTEM_DEFAULT,
|
|
2155
2173
|
MAX_TOOL_LOOPS,
|
|
2156
2174
|
CHANNEL_HISTORY_LIMIT,
|
package/dist/index.d.ts
CHANGED
|
@@ -830,8 +830,8 @@ export declare const utils: {
|
|
|
830
830
|
"openai-compatible": "openai-compatible";
|
|
831
831
|
}>;
|
|
832
832
|
apiKey: import("zod").ZodString;
|
|
833
|
-
model: import("zod").ZodString;
|
|
834
833
|
baseUrl: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodURL>, import("zod").ZodLiteral<"">]>;
|
|
834
|
+
model: import("zod").ZodString;
|
|
835
835
|
systemPrompt: import("zod").ZodOptional<import("zod").ZodString>;
|
|
836
836
|
}, import("zod/v4/core").$strip>;
|
|
837
837
|
ORGANIZATION_UPDATE_LLM: import("zod").ZodObject<{
|
|
@@ -861,8 +861,8 @@ export declare const utils: {
|
|
|
861
861
|
"openai-compatible": "openai-compatible";
|
|
862
862
|
}>>;
|
|
863
863
|
apiKey: import("zod").ZodOptional<import("zod").ZodString>;
|
|
864
|
-
model: import("zod").ZodOptional<import("zod").ZodString>;
|
|
865
864
|
baseUrl: import("zod").ZodNullable<import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodURL>, import("zod").ZodLiteral<"">]>>;
|
|
865
|
+
model: import("zod").ZodOptional<import("zod").ZodString>;
|
|
866
866
|
systemPrompt: import("zod").ZodNullable<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
867
867
|
}, import("zod/v4/core").$strip>;
|
|
868
868
|
ORGANIZATION_REMOVE_LLM: import("zod").ZodObject<{
|
|
@@ -2071,6 +2071,9 @@ export declare const utils: {
|
|
|
2071
2071
|
model: string;
|
|
2072
2072
|
label: string;
|
|
2073
2073
|
}[];
|
|
2074
|
+
LLM_CATALOG_CUSTOM_KEY: string;
|
|
2075
|
+
LLM_BASE_URL_REQUIRED_MESSAGE: string;
|
|
2076
|
+
LLM_BASE_URL_INVALID_MESSAGE: string;
|
|
2074
2077
|
LLM_SYSTEM_DEFAULT: string;
|
|
2075
2078
|
MAX_TOOL_LOOPS: number;
|
|
2076
2079
|
CHANNEL_HISTORY_LIMIT: number;
|
|
@@ -2428,6 +2431,9 @@ export declare const utils: {
|
|
|
2428
2431
|
projectPathIssue: (path: string, taken?: Iterable<string>) => ProjectPathIssue | null;
|
|
2429
2432
|
ipv4InPrivateRange: (host: string) => boolean;
|
|
2430
2433
|
isBlockedHost: (hostname: string) => boolean;
|
|
2434
|
+
requiresLlmBaseUrl: (provider?: string | null) => boolean;
|
|
2435
|
+
isAllowedLlmBaseUrl: (value: string) => boolean;
|
|
2436
|
+
llmBaseUrlIssue: (provider?: string | null, baseUrl?: string | null) => string | null;
|
|
2431
2437
|
validateMessageVariables: (messages: Array<{
|
|
2432
2438
|
role: string;
|
|
2433
2439
|
content: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAyCvD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EACjC,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,UAAU,EAGX,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAU9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAW3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA5HI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAlHF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA4DpB,CAAD;oBACC,CAAD;;;;aAyByC,CAAC;mBAC/B,CAAC;WAAsB,CAAC;gBACf,CAAC;gBAA2B,CAAC;;;mBAuBtC,CAAC;oBAAuB,CAAA;;;;;;;;;;;;;;;kBA5DlC,CAAC;;;;;;;sBAoCgB,CAAC;;;;;;;;;;;;;;;CAmRnB,CAAC;AAEF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EAChC,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACX,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const vendorError_1 = require("./vendorError");
|
|
|
26
26
|
const deadline_1 = require("./deadline");
|
|
27
27
|
const customCodeProject_1 = require("./customCodeProject");
|
|
28
28
|
const ssrf_1 = require("./ssrf");
|
|
29
|
+
const llmBaseUrl_1 = require("./llmBaseUrl");
|
|
29
30
|
const formatRelative_1 = require("./formatRelative");
|
|
30
31
|
const formatRelativeTime_1 = require("./formatRelativeTime");
|
|
31
32
|
const languageCookieDomain_1 = require("./languageCookieDomain");
|
|
@@ -110,6 +111,9 @@ exports.utils = {
|
|
|
110
111
|
projectPathIssue: customCodeProject_1.projectPathIssue,
|
|
111
112
|
ipv4InPrivateRange: ssrf_1.ipv4InPrivateRange,
|
|
112
113
|
isBlockedHost: ssrf_1.isBlockedHost,
|
|
114
|
+
requiresLlmBaseUrl: llmBaseUrl_1.requiresLlmBaseUrl,
|
|
115
|
+
isAllowedLlmBaseUrl: llmBaseUrl_1.isAllowedLlmBaseUrl,
|
|
116
|
+
llmBaseUrlIssue: llmBaseUrl_1.llmBaseUrlIssue,
|
|
113
117
|
validateMessageVariables: validateMessageVariables_1.validateMessageVariables,
|
|
114
118
|
formatRelative: formatRelative_1.formatRelative,
|
|
115
119
|
formatRelativeTime: formatRelativeTime_1.formatRelativeTime,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules an `organization_llm.base_url` has to satisfy, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Two write paths need them and neither can see the whole picture alone: the
|
|
5
|
+
* create schema validates a complete payload, while an update is a partial, so
|
|
6
|
+
* the provider may arrive in the request and the base URL may only exist on the
|
|
7
|
+
* stored row. Keeping the rules here means the two can't drift into disagreeing
|
|
8
|
+
* about what a valid row is — the same reason every other write in this package
|
|
9
|
+
* goes through a shared validator rather than an inline check.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Which providers need a base URL of their own.
|
|
13
|
+
*
|
|
14
|
+
* Every other provider has an endpoint baked into its SDK. `openai-compatible`
|
|
15
|
+
* has none: it is the escape hatch for services that merely speak the OpenAI
|
|
16
|
+
* protocol, so without a base URL the adapter would send the customer's key to
|
|
17
|
+
* `api.openai.com` and fail somewhere far from the form they typed it into.
|
|
18
|
+
*/
|
|
19
|
+
export declare const requiresLlmBaseUrl: (provider?: string | null) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a base URL is safe to send a request to.
|
|
22
|
+
*
|
|
23
|
+
* This is an outbound fetch target chosen by a customer, on a worker holding
|
|
24
|
+
* their decrypted API key, so it gets the same screening as every other
|
|
25
|
+
* caller-supplied URL: `https` only, because the key travels in a header, and
|
|
26
|
+
* no loopback, private or link-local host. The Workers runtime can't resolve
|
|
27
|
+
* DNS, so — as everywhere else `isBlockedHost` is used — this screens literal
|
|
28
|
+
* hosts and IPs and is not a defense against DNS rebinding.
|
|
29
|
+
*/
|
|
30
|
+
export declare const isAllowedLlmBaseUrl: (value: string) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Both rules against one provider/base-URL pair: the message to reject with, or
|
|
33
|
+
* `null` when the pair is acceptable. The messages are constants so
|
|
34
|
+
* `localizeZodIssue` can translate them, and both carry a word `matchStatus`
|
|
35
|
+
* recognises — the update path throws this as a plain Error rather than a
|
|
36
|
+
* ZodError, and an unrecognised message there answers 500 where it means 400.
|
|
37
|
+
*/
|
|
38
|
+
export declare const llmBaseUrlIssue: (provider?: string | null, baseUrl?: string | null) => string | null;
|
|
39
|
+
//# sourceMappingURL=llmBaseUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmBaseUrl.d.ts","sourceRoot":"","sources":["../src/llmBaseUrl.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,GAAG,IAAI,KAAG,OACP,CAAC;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,KAAG,OASnD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,MAAM,GAAG,IAAI,EACxB,UAAU,MAAM,GAAG,IAAI,KACtB,MAAM,GAAG,IAQX,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.llmBaseUrlIssue = exports.isAllowedLlmBaseUrl = exports.requiresLlmBaseUrl = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const ssrf_1 = require("./ssrf");
|
|
6
|
+
/**
|
|
7
|
+
* The rules an `organization_llm.base_url` has to satisfy, in one place.
|
|
8
|
+
*
|
|
9
|
+
* Two write paths need them and neither can see the whole picture alone: the
|
|
10
|
+
* create schema validates a complete payload, while an update is a partial, so
|
|
11
|
+
* the provider may arrive in the request and the base URL may only exist on the
|
|
12
|
+
* stored row. Keeping the rules here means the two can't drift into disagreeing
|
|
13
|
+
* about what a valid row is — the same reason every other write in this package
|
|
14
|
+
* goes through a shared validator rather than an inline check.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Which providers need a base URL of their own.
|
|
18
|
+
*
|
|
19
|
+
* Every other provider has an endpoint baked into its SDK. `openai-compatible`
|
|
20
|
+
* has none: it is the escape hatch for services that merely speak the OpenAI
|
|
21
|
+
* protocol, so without a base URL the adapter would send the customer's key to
|
|
22
|
+
* `api.openai.com` and fail somewhere far from the form they typed it into.
|
|
23
|
+
*/
|
|
24
|
+
const requiresLlmBaseUrl = (provider) => provider === constants_1.constants.LLM_PROVIDER_OPENAI_COMPATIBLE;
|
|
25
|
+
exports.requiresLlmBaseUrl = requiresLlmBaseUrl;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a base URL is safe to send a request to.
|
|
28
|
+
*
|
|
29
|
+
* This is an outbound fetch target chosen by a customer, on a worker holding
|
|
30
|
+
* their decrypted API key, so it gets the same screening as every other
|
|
31
|
+
* caller-supplied URL: `https` only, because the key travels in a header, and
|
|
32
|
+
* no loopback, private or link-local host. The Workers runtime can't resolve
|
|
33
|
+
* DNS, so — as everywhere else `isBlockedHost` is used — this screens literal
|
|
34
|
+
* hosts and IPs and is not a defense against DNS rebinding.
|
|
35
|
+
*/
|
|
36
|
+
const isAllowedLlmBaseUrl = (value) => {
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(value);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (parsed.protocol !== 'https:')
|
|
45
|
+
return false;
|
|
46
|
+
return !(0, ssrf_1.isBlockedHost)(parsed.hostname);
|
|
47
|
+
};
|
|
48
|
+
exports.isAllowedLlmBaseUrl = isAllowedLlmBaseUrl;
|
|
49
|
+
/**
|
|
50
|
+
* Both rules against one provider/base-URL pair: the message to reject with, or
|
|
51
|
+
* `null` when the pair is acceptable. The messages are constants so
|
|
52
|
+
* `localizeZodIssue` can translate them, and both carry a word `matchStatus`
|
|
53
|
+
* recognises — the update path throws this as a plain Error rather than a
|
|
54
|
+
* ZodError, and an unrecognised message there answers 500 where it means 400.
|
|
55
|
+
*/
|
|
56
|
+
const llmBaseUrlIssue = (provider, baseUrl) => {
|
|
57
|
+
if ((0, exports.requiresLlmBaseUrl)(provider) && !baseUrl) {
|
|
58
|
+
return constants_1.constants.LLM_BASE_URL_REQUIRED_MESSAGE;
|
|
59
|
+
}
|
|
60
|
+
if (baseUrl && !(0, exports.isAllowedLlmBaseUrl)(baseUrl)) {
|
|
61
|
+
return constants_1.constants.LLM_BASE_URL_INVALID_MESSAGE;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
exports.llmBaseUrlIssue = llmBaseUrlIssue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localizeZodIssue.d.ts","sourceRoot":"","sources":["../src/localizeZodIssue.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"localizeZodIssue.d.ts","sourceRoot":"","sources":["../src/localizeZodIssue.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;CACtB;AAwDD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,GAAG,IAAI,KAAG,MAK3D,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,GAC3B,OAAO,YAAY,EACnB,UAAU,MAAM,KACf,MAmBF,CAAC"}
|
package/dist/localizeZodIssue.js
CHANGED
|
@@ -43,6 +43,12 @@ const CUSTOM_MESSAGES = {
|
|
|
43
43
|
},
|
|
44
44
|
[constants_1.constants.CUSTOM_CODE_RESOURCE_FILE_TOO_LARGE_MESSAGE]: {
|
|
45
45
|
es: `Los bytes del archivo superan el límite de ${constants_1.constants.CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES / (1024 * 1024)}MB`
|
|
46
|
+
},
|
|
47
|
+
[constants_1.constants.LLM_BASE_URL_REQUIRED_MESSAGE]: {
|
|
48
|
+
es: 'Se requiere una URL base para un modelo compatible con OpenAI'
|
|
49
|
+
},
|
|
50
|
+
[constants_1.constants.LLM_BASE_URL_INVALID_MESSAGE]: {
|
|
51
|
+
es: 'La URL base debe ser una dirección https:// pública'
|
|
46
52
|
}
|
|
47
53
|
};
|
|
48
54
|
// Built once. `esLocale()` allocates its dictionaries on every call, and this
|
package/dist/schema.d.ts
CHANGED
|
@@ -1215,8 +1215,8 @@ export declare const Schema: {
|
|
|
1215
1215
|
"openai-compatible": "openai-compatible";
|
|
1216
1216
|
}>;
|
|
1217
1217
|
apiKey: z.ZodString;
|
|
1218
|
-
model: z.ZodString;
|
|
1219
1218
|
baseUrl: z.ZodUnion<[z.ZodOptional<z.ZodURL>, z.ZodLiteral<"">]>;
|
|
1219
|
+
model: z.ZodString;
|
|
1220
1220
|
systemPrompt: z.ZodOptional<z.ZodString>;
|
|
1221
1221
|
}, z.core.$strip>;
|
|
1222
1222
|
ORGANIZATION_UPDATE_LLM: z.ZodObject<{
|
|
@@ -1246,8 +1246,8 @@ export declare const Schema: {
|
|
|
1246
1246
|
"openai-compatible": "openai-compatible";
|
|
1247
1247
|
}>>;
|
|
1248
1248
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
1249
|
-
model: z.ZodOptional<z.ZodString>;
|
|
1250
1249
|
baseUrl: z.ZodNullable<z.ZodUnion<[z.ZodOptional<z.ZodURL>, z.ZodLiteral<"">]>>;
|
|
1250
|
+
model: z.ZodOptional<z.ZodString>;
|
|
1251
1251
|
systemPrompt: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
1252
1252
|
}, z.core.$strip>;
|
|
1253
1253
|
ORGANIZATION_REMOVE_LLM: z.ZodObject<{
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAixBxB,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FrB,CAAC;AAyDN,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCpB,CAAC;AAWH,QAAA,MAAM,kBAAkB;;;;;;;;;;iBA+CtB,CAAC;AAiCH,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAapB,CAAC;AAYH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCxB,CAAC;AA4LH,QAAA,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DpC,CAAC;AA4BL,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAkDzB,CAAC;AAwGH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0GlB,CAAC;AAGF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAG1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAIlE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAMtE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAGtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAIvE,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAC5C,OAAO,kCAAkC,CAC1C,CAAC;AAKF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAKD,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAC1E;AAMD,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACtC"}
|
package/dist/schema.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Schema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const constants_1 = require("./constants");
|
|
6
|
+
const llmBaseUrl_1 = require("./llmBaseUrl");
|
|
6
7
|
const slug_1 = require("./slug");
|
|
7
8
|
const reservedToolName_1 = require("./reservedToolName");
|
|
8
9
|
const slugifyTitle_1 = require("./slugifyTitle");
|
|
@@ -491,29 +492,52 @@ const ORGANIZATION_LIST_LLM = zod_1.z.object({
|
|
|
491
492
|
userId: zod_1.z.uuid(),
|
|
492
493
|
organizationId: zod_1.z.uuid()
|
|
493
494
|
});
|
|
494
|
-
|
|
495
|
+
// The two rules live in `llmBaseUrl.ts`, because the update controller applies
|
|
496
|
+
// them again to the merged row — the only place both halves are visible. Here
|
|
497
|
+
// they are just the field validator and the cross-field check.
|
|
498
|
+
const LLM_BASE_URL = zod_1.z
|
|
499
|
+
.url()
|
|
500
|
+
.max(500)
|
|
501
|
+
.refine(llmBaseUrl_1.isAllowedLlmBaseUrl, constants_1.constants.LLM_BASE_URL_INVALID_MESSAGE);
|
|
502
|
+
const BASE_URL_REQUIRED = {
|
|
503
|
+
message: constants_1.constants.LLM_BASE_URL_REQUIRED_MESSAGE,
|
|
504
|
+
path: ['baseUrl']
|
|
505
|
+
};
|
|
506
|
+
const hasBaseUrlWhenRequired = (provider, baseUrl) => !(0, llmBaseUrl_1.requiresLlmBaseUrl)(provider) || !!baseUrl;
|
|
507
|
+
// The base-URL rule is a cross-field check, and zod cannot `.omit()` from a
|
|
508
|
+
// schema that already carries one. So the shape stays a plain object — the
|
|
509
|
+
// `_VIEW` variants below derive from it — and the refinement is applied last,
|
|
510
|
+
// to each schema in turn, which also keeps the two in step.
|
|
511
|
+
const ORGANIZATION_CREATE_LLM_SHAPE = zod_1.z.object({
|
|
495
512
|
name: zod_1.z.string().min(1).max(200),
|
|
496
513
|
provider: zod_1.z.enum(constants_1.constants.LLM_PROVIDERS),
|
|
497
514
|
model: zod_1.z.string().min(1).max(200),
|
|
498
|
-
baseUrl:
|
|
515
|
+
baseUrl: LLM_BASE_URL.optional().or(zod_1.z.literal('')),
|
|
499
516
|
apiKey: zod_1.z.string().min(1).max(500),
|
|
500
517
|
systemPrompt: zod_1.z.string().max(10000).optional(),
|
|
501
518
|
config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
|
|
502
519
|
userId: zod_1.z.uuid(),
|
|
503
520
|
organizationId: zod_1.z.uuid()
|
|
504
521
|
});
|
|
505
|
-
const
|
|
522
|
+
const ORGANIZATION_CREATE_LLM = ORGANIZATION_CREATE_LLM_SHAPE.refine(v => hasBaseUrlWhenRequired(v.provider, v.baseUrl), BASE_URL_REQUIRED);
|
|
523
|
+
// An update is a partial: an omitted field keeps its stored value, so the
|
|
524
|
+
// base-URL rule can only be checked here when the caller sends the provider and
|
|
525
|
+
// the base URL together. The controller re-checks it against the merged row,
|
|
526
|
+
// which is the only place both halves are known.
|
|
527
|
+
const ORGANIZATION_UPDATE_LLM_SHAPE = zod_1.z.object({
|
|
506
528
|
llmId: zod_1.z.uuid(),
|
|
507
529
|
name: zod_1.z.string().min(1).max(200).optional(),
|
|
508
530
|
provider: zod_1.z.enum(constants_1.constants.LLM_PROVIDERS).optional(),
|
|
509
531
|
model: zod_1.z.string().min(1).max(200).optional(),
|
|
510
|
-
baseUrl:
|
|
532
|
+
baseUrl: LLM_BASE_URL.optional().or(zod_1.z.literal('')).nullable(),
|
|
511
533
|
apiKey: zod_1.z.string().min(1).max(500).optional(),
|
|
512
534
|
systemPrompt: zod_1.z.string().max(10000).optional().nullable(),
|
|
513
535
|
config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().nullable(),
|
|
514
536
|
userId: zod_1.z.uuid(),
|
|
515
537
|
organizationId: zod_1.z.uuid()
|
|
516
538
|
});
|
|
539
|
+
const refineUpdateBaseUrl = (schema) => schema.refine(v => v.baseUrl === undefined || hasBaseUrlWhenRequired(v.provider, v.baseUrl), BASE_URL_REQUIRED);
|
|
540
|
+
const ORGANIZATION_UPDATE_LLM = refineUpdateBaseUrl(ORGANIZATION_UPDATE_LLM_SHAPE);
|
|
517
541
|
const ORGANIZATION_REMOVE_LLM = zod_1.z.object({
|
|
518
542
|
llmId: zod_1.z.uuid(),
|
|
519
543
|
userId: zod_1.z.uuid(),
|
|
@@ -561,15 +585,15 @@ const ACCESS_TOKEN_CREATE_VIEW = ACCESS_TOKEN_CREATE.omit({
|
|
|
561
585
|
organizationId: true,
|
|
562
586
|
projectId: true
|
|
563
587
|
});
|
|
564
|
-
const ORGANIZATION_CREATE_LLM_VIEW =
|
|
588
|
+
const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM_SHAPE.omit({
|
|
565
589
|
userId: true,
|
|
566
590
|
organizationId: true
|
|
567
|
-
});
|
|
568
|
-
const ORGANIZATION_UPDATE_LLM_VIEW =
|
|
591
|
+
}).refine(v => hasBaseUrlWhenRequired(v.provider, v.baseUrl), BASE_URL_REQUIRED);
|
|
592
|
+
const ORGANIZATION_UPDATE_LLM_VIEW = refineUpdateBaseUrl(ORGANIZATION_UPDATE_LLM_SHAPE.omit({
|
|
569
593
|
llmId: true,
|
|
570
594
|
userId: true,
|
|
571
595
|
organizationId: true
|
|
572
|
-
});
|
|
596
|
+
}));
|
|
573
597
|
// `channel.config` is an open bag, so this stays loose — it only pins down the
|
|
574
598
|
// keys we actually read, and passes everything else through untouched.
|
|
575
599
|
// `debounceMs` is how long a burst from one participant is buffered before the
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -622,8 +622,8 @@ const LLM_CATALOG: ReadonlyArray<{
|
|
|
622
622
|
}> = [
|
|
623
623
|
{
|
|
624
624
|
provider: LLM_PROVIDER_GOOGLE,
|
|
625
|
-
model: 'gemini-3-flash
|
|
626
|
-
label: 'Gemini 3 Flash'
|
|
625
|
+
model: 'gemini-3.8-flash',
|
|
626
|
+
label: 'Gemini 3.8 Flash'
|
|
627
627
|
},
|
|
628
628
|
{
|
|
629
629
|
provider: LLM_PROVIDER_GOOGLE,
|
|
@@ -632,13 +632,13 @@ const LLM_CATALOG: ReadonlyArray<{
|
|
|
632
632
|
},
|
|
633
633
|
{
|
|
634
634
|
provider: LLM_PROVIDER_OPENAI,
|
|
635
|
-
model: 'gpt-
|
|
636
|
-
label: 'GPT-
|
|
635
|
+
model: 'gpt-5.4-mini',
|
|
636
|
+
label: 'GPT-5.4 mini'
|
|
637
637
|
},
|
|
638
638
|
{
|
|
639
639
|
provider: LLM_PROVIDER_OPENAI,
|
|
640
|
-
model: 'gpt-
|
|
641
|
-
label: 'GPT-
|
|
640
|
+
model: 'gpt-5.5',
|
|
641
|
+
label: 'GPT-5.5'
|
|
642
642
|
},
|
|
643
643
|
{
|
|
644
644
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
@@ -647,16 +647,35 @@ const LLM_CATALOG: ReadonlyArray<{
|
|
|
647
647
|
},
|
|
648
648
|
{
|
|
649
649
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
650
|
-
model: 'claude-sonnet-
|
|
651
|
-
label: 'Claude Sonnet
|
|
650
|
+
model: 'claude-sonnet-5',
|
|
651
|
+
label: 'Claude Sonnet 5'
|
|
652
652
|
},
|
|
653
653
|
{
|
|
654
654
|
provider: LLM_PROVIDER_ANTHROPIC,
|
|
655
|
-
model: 'claude-opus-
|
|
656
|
-
label: 'Claude
|
|
655
|
+
model: 'claude-opus-5',
|
|
656
|
+
label: 'Claude Opus 5'
|
|
657
657
|
}
|
|
658
658
|
];
|
|
659
659
|
|
|
660
|
+
// The picker's escape hatch. Anything that speaks the OpenAI chat-completions
|
|
661
|
+
// protocol — a gateway, an aggregator, a self-hosted server — is reachable
|
|
662
|
+
// without a catalog row: the customer supplies the model id and the base URL,
|
|
663
|
+
// and the request goes out through the OpenAI adapter. Not a provider value, so
|
|
664
|
+
// it can never reach the database; the dashboard translates it into
|
|
665
|
+
// LLM_PROVIDER_OPENAI_COMPATIBLE on submit.
|
|
666
|
+
const LLM_CATALOG_CUSTOM_KEY = 'custom';
|
|
667
|
+
|
|
668
|
+
// Validation messages for an organization LLM's base URL. Constants rather than
|
|
669
|
+
// inline strings because localizeZodIssue keys its translations on the exact
|
|
670
|
+
// English text, so the two files have to name the same value. Both also carry a
|
|
671
|
+
// word matchStatus recognises ("required", "must be"): the update path re-checks
|
|
672
|
+
// the merged row and throws the message as a plain Error, and anything
|
|
673
|
+
// unrecognised there becomes an opaque 500 instead of a 400.
|
|
674
|
+
const LLM_BASE_URL_REQUIRED_MESSAGE =
|
|
675
|
+
'A base URL is required for an OpenAI-compatible model';
|
|
676
|
+
const LLM_BASE_URL_INVALID_MESSAGE =
|
|
677
|
+
'Base URL must be a public https:// address';
|
|
678
|
+
|
|
660
679
|
const LLM_SYSTEM_DEFAULT = 'SYSTEM_DEFAULT';
|
|
661
680
|
|
|
662
681
|
const MAX_TOOL_LOOPS = 8;
|
|
@@ -2128,7 +2147,7 @@ const POLAR_SERVER_PRODUCTION = 'production' as 'production';
|
|
|
2128
2147
|
const CONSENT_DOCUMENT_TERMS = 'terms' as 'terms';
|
|
2129
2148
|
const CONSENT_DOCUMENT_PRIVACY = 'privacy' as 'privacy';
|
|
2130
2149
|
const CONSENT_DOCUMENTS = [CONSENT_DOCUMENT_TERMS, CONSENT_DOCUMENT_PRIVACY];
|
|
2131
|
-
const CONSENT_CURRENT_VERSION = '2026-
|
|
2150
|
+
const CONSENT_CURRENT_VERSION = '2026-09-10';
|
|
2132
2151
|
|
|
2133
2152
|
const CONSENT_SOURCE_SIGNUP = 'signup' as 'signup';
|
|
2134
2153
|
const CONSENT_SOURCE_REACCEPT = 'reaccept' as 'reaccept';
|
|
@@ -2506,6 +2525,9 @@ export const constants = {
|
|
|
2506
2525
|
DEFAULT_LLM_MODEL,
|
|
2507
2526
|
DEFAULT_LLM_SYSTEM_PROMPT,
|
|
2508
2527
|
LLM_CATALOG,
|
|
2528
|
+
LLM_CATALOG_CUSTOM_KEY,
|
|
2529
|
+
LLM_BASE_URL_REQUIRED_MESSAGE,
|
|
2530
|
+
LLM_BASE_URL_INVALID_MESSAGE,
|
|
2509
2531
|
LLM_SYSTEM_DEFAULT,
|
|
2510
2532
|
MAX_TOOL_LOOPS,
|
|
2511
2533
|
CHANNEL_HISTORY_LIMIT,
|
package/src/index.ts
CHANGED
|
@@ -134,6 +134,11 @@ import type {
|
|
|
134
134
|
ResolveAttachmentResult
|
|
135
135
|
} from './attachment';
|
|
136
136
|
import { ipv4InPrivateRange, isBlockedHost } from './ssrf';
|
|
137
|
+
import {
|
|
138
|
+
requiresLlmBaseUrl,
|
|
139
|
+
isAllowedLlmBaseUrl,
|
|
140
|
+
llmBaseUrlIssue
|
|
141
|
+
} from './llmBaseUrl';
|
|
137
142
|
import { formatRelative } from './formatRelative';
|
|
138
143
|
import { formatRelativeTime } from './formatRelativeTime';
|
|
139
144
|
import { languageCookieDomain } from './languageCookieDomain';
|
|
@@ -297,6 +302,9 @@ export const utils = {
|
|
|
297
302
|
projectPathIssue,
|
|
298
303
|
ipv4InPrivateRange,
|
|
299
304
|
isBlockedHost,
|
|
305
|
+
requiresLlmBaseUrl,
|
|
306
|
+
isAllowedLlmBaseUrl,
|
|
307
|
+
llmBaseUrlIssue,
|
|
300
308
|
validateMessageVariables,
|
|
301
309
|
formatRelative,
|
|
302
310
|
formatRelativeTime,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { constants } from './constants';
|
|
2
|
+
import { isBlockedHost } from './ssrf';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The rules an `organization_llm.base_url` has to satisfy, in one place.
|
|
6
|
+
*
|
|
7
|
+
* Two write paths need them and neither can see the whole picture alone: the
|
|
8
|
+
* create schema validates a complete payload, while an update is a partial, so
|
|
9
|
+
* the provider may arrive in the request and the base URL may only exist on the
|
|
10
|
+
* stored row. Keeping the rules here means the two can't drift into disagreeing
|
|
11
|
+
* about what a valid row is — the same reason every other write in this package
|
|
12
|
+
* goes through a shared validator rather than an inline check.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Which providers need a base URL of their own.
|
|
17
|
+
*
|
|
18
|
+
* Every other provider has an endpoint baked into its SDK. `openai-compatible`
|
|
19
|
+
* has none: it is the escape hatch for services that merely speak the OpenAI
|
|
20
|
+
* protocol, so without a base URL the adapter would send the customer's key to
|
|
21
|
+
* `api.openai.com` and fail somewhere far from the form they typed it into.
|
|
22
|
+
*/
|
|
23
|
+
export const requiresLlmBaseUrl = (provider?: string | null): boolean =>
|
|
24
|
+
provider === constants.LLM_PROVIDER_OPENAI_COMPATIBLE;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether a base URL is safe to send a request to.
|
|
28
|
+
*
|
|
29
|
+
* This is an outbound fetch target chosen by a customer, on a worker holding
|
|
30
|
+
* their decrypted API key, so it gets the same screening as every other
|
|
31
|
+
* caller-supplied URL: `https` only, because the key travels in a header, and
|
|
32
|
+
* no loopback, private or link-local host. The Workers runtime can't resolve
|
|
33
|
+
* DNS, so — as everywhere else `isBlockedHost` is used — this screens literal
|
|
34
|
+
* hosts and IPs and is not a defense against DNS rebinding.
|
|
35
|
+
*/
|
|
36
|
+
export const isAllowedLlmBaseUrl = (value: string): boolean => {
|
|
37
|
+
let parsed: URL;
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(value);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (parsed.protocol !== 'https:') return false;
|
|
44
|
+
return !isBlockedHost(parsed.hostname);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Both rules against one provider/base-URL pair: the message to reject with, or
|
|
49
|
+
* `null` when the pair is acceptable. The messages are constants so
|
|
50
|
+
* `localizeZodIssue` can translate them, and both carry a word `matchStatus`
|
|
51
|
+
* recognises — the update path throws this as a plain Error rather than a
|
|
52
|
+
* ZodError, and an unrecognised message there answers 500 where it means 400.
|
|
53
|
+
*/
|
|
54
|
+
export const llmBaseUrlIssue = (
|
|
55
|
+
provider?: string | null,
|
|
56
|
+
baseUrl?: string | null
|
|
57
|
+
): string | null => {
|
|
58
|
+
if (requiresLlmBaseUrl(provider) && !baseUrl) {
|
|
59
|
+
return constants.LLM_BASE_URL_REQUIRED_MESSAGE;
|
|
60
|
+
}
|
|
61
|
+
if (baseUrl && !isAllowedLlmBaseUrl(baseUrl)) {
|
|
62
|
+
return constants.LLM_BASE_URL_INVALID_MESSAGE;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
package/src/localizeZodIssue.ts
CHANGED
|
@@ -74,6 +74,12 @@ const CUSTOM_MESSAGES: Record<string, Record<string, string>> = {
|
|
|
74
74
|
},
|
|
75
75
|
[constants.CUSTOM_CODE_RESOURCE_FILE_TOO_LARGE_MESSAGE]: {
|
|
76
76
|
es: `Los bytes del archivo superan el límite de ${constants.CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES / (1024 * 1024)}MB`
|
|
77
|
+
},
|
|
78
|
+
[constants.LLM_BASE_URL_REQUIRED_MESSAGE]: {
|
|
79
|
+
es: 'Se requiere una URL base para un modelo compatible con OpenAI'
|
|
80
|
+
},
|
|
81
|
+
[constants.LLM_BASE_URL_INVALID_MESSAGE]: {
|
|
82
|
+
es: 'La URL base debe ser una dirección https:// pública'
|
|
77
83
|
}
|
|
78
84
|
};
|
|
79
85
|
|
package/src/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
import { constants } from './constants';
|
|
4
|
+
import { isAllowedLlmBaseUrl, requiresLlmBaseUrl } from './llmBaseUrl';
|
|
4
5
|
import { isReservedSlug, isValidSlugFormat } from './slug';
|
|
5
6
|
import { isReservedToolName } from './reservedToolName';
|
|
6
7
|
import { slugifyTitle } from './slugifyTitle';
|
|
@@ -572,11 +573,33 @@ const ORGANIZATION_LIST_LLM = z.object({
|
|
|
572
573
|
organizationId: z.uuid()
|
|
573
574
|
});
|
|
574
575
|
|
|
575
|
-
|
|
576
|
+
// The two rules live in `llmBaseUrl.ts`, because the update controller applies
|
|
577
|
+
// them again to the merged row — the only place both halves are visible. Here
|
|
578
|
+
// they are just the field validator and the cross-field check.
|
|
579
|
+
const LLM_BASE_URL = z
|
|
580
|
+
.url()
|
|
581
|
+
.max(500)
|
|
582
|
+
.refine(isAllowedLlmBaseUrl, constants.LLM_BASE_URL_INVALID_MESSAGE);
|
|
583
|
+
|
|
584
|
+
const BASE_URL_REQUIRED = {
|
|
585
|
+
message: constants.LLM_BASE_URL_REQUIRED_MESSAGE,
|
|
586
|
+
path: ['baseUrl']
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const hasBaseUrlWhenRequired = (
|
|
590
|
+
provider: string | undefined,
|
|
591
|
+
baseUrl: unknown
|
|
592
|
+
) => !requiresLlmBaseUrl(provider) || !!baseUrl;
|
|
593
|
+
|
|
594
|
+
// The base-URL rule is a cross-field check, and zod cannot `.omit()` from a
|
|
595
|
+
// schema that already carries one. So the shape stays a plain object — the
|
|
596
|
+
// `_VIEW` variants below derive from it — and the refinement is applied last,
|
|
597
|
+
// to each schema in turn, which also keeps the two in step.
|
|
598
|
+
const ORGANIZATION_CREATE_LLM_SHAPE = z.object({
|
|
576
599
|
name: z.string().min(1).max(200),
|
|
577
600
|
provider: z.enum(constants.LLM_PROVIDERS),
|
|
578
601
|
model: z.string().min(1).max(200),
|
|
579
|
-
baseUrl:
|
|
602
|
+
baseUrl: LLM_BASE_URL.optional().or(z.literal('')),
|
|
580
603
|
apiKey: z.string().min(1).max(500),
|
|
581
604
|
systemPrompt: z.string().max(10000).optional(),
|
|
582
605
|
config: z.record(z.string(), z.any()).optional(),
|
|
@@ -584,12 +607,21 @@ const ORGANIZATION_CREATE_LLM = z.object({
|
|
|
584
607
|
organizationId: z.uuid()
|
|
585
608
|
});
|
|
586
609
|
|
|
587
|
-
const
|
|
610
|
+
const ORGANIZATION_CREATE_LLM = ORGANIZATION_CREATE_LLM_SHAPE.refine(
|
|
611
|
+
v => hasBaseUrlWhenRequired(v.provider, v.baseUrl),
|
|
612
|
+
BASE_URL_REQUIRED
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// An update is a partial: an omitted field keeps its stored value, so the
|
|
616
|
+
// base-URL rule can only be checked here when the caller sends the provider and
|
|
617
|
+
// the base URL together. The controller re-checks it against the merged row,
|
|
618
|
+
// which is the only place both halves are known.
|
|
619
|
+
const ORGANIZATION_UPDATE_LLM_SHAPE = z.object({
|
|
588
620
|
llmId: z.uuid(),
|
|
589
621
|
name: z.string().min(1).max(200).optional(),
|
|
590
622
|
provider: z.enum(constants.LLM_PROVIDERS).optional(),
|
|
591
623
|
model: z.string().min(1).max(200).optional(),
|
|
592
|
-
baseUrl:
|
|
624
|
+
baseUrl: LLM_BASE_URL.optional().or(z.literal('')).nullable(),
|
|
593
625
|
apiKey: z.string().min(1).max(500).optional(),
|
|
594
626
|
systemPrompt: z.string().max(10000).optional().nullable(),
|
|
595
627
|
config: z.record(z.string(), z.any()).optional().nullable(),
|
|
@@ -597,6 +629,21 @@ const ORGANIZATION_UPDATE_LLM = z.object({
|
|
|
597
629
|
organizationId: z.uuid()
|
|
598
630
|
});
|
|
599
631
|
|
|
632
|
+
const refineUpdateBaseUrl = <
|
|
633
|
+
T extends z.ZodType<{ provider?: string; baseUrl?: unknown }>
|
|
634
|
+
>(
|
|
635
|
+
schema: T
|
|
636
|
+
) =>
|
|
637
|
+
schema.refine(
|
|
638
|
+
v =>
|
|
639
|
+
v.baseUrl === undefined || hasBaseUrlWhenRequired(v.provider, v.baseUrl),
|
|
640
|
+
BASE_URL_REQUIRED
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
const ORGANIZATION_UPDATE_LLM = refineUpdateBaseUrl(
|
|
644
|
+
ORGANIZATION_UPDATE_LLM_SHAPE
|
|
645
|
+
);
|
|
646
|
+
|
|
600
647
|
const ORGANIZATION_REMOVE_LLM = z.object({
|
|
601
648
|
llmId: z.uuid(),
|
|
602
649
|
userId: z.uuid(),
|
|
@@ -649,16 +696,21 @@ const ACCESS_TOKEN_CREATE_VIEW = ACCESS_TOKEN_CREATE.omit({
|
|
|
649
696
|
projectId: true
|
|
650
697
|
});
|
|
651
698
|
|
|
652
|
-
const ORGANIZATION_CREATE_LLM_VIEW =
|
|
699
|
+
const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM_SHAPE.omit({
|
|
653
700
|
userId: true,
|
|
654
701
|
organizationId: true
|
|
655
|
-
})
|
|
702
|
+
}).refine(
|
|
703
|
+
v => hasBaseUrlWhenRequired(v.provider, v.baseUrl),
|
|
704
|
+
BASE_URL_REQUIRED
|
|
705
|
+
);
|
|
656
706
|
|
|
657
|
-
const ORGANIZATION_UPDATE_LLM_VIEW =
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
707
|
+
const ORGANIZATION_UPDATE_LLM_VIEW = refineUpdateBaseUrl(
|
|
708
|
+
ORGANIZATION_UPDATE_LLM_SHAPE.omit({
|
|
709
|
+
llmId: true,
|
|
710
|
+
userId: true,
|
|
711
|
+
organizationId: true
|
|
712
|
+
})
|
|
713
|
+
);
|
|
662
714
|
|
|
663
715
|
// `channel.config` is an open bag, so this stays loose — it only pins down the
|
|
664
716
|
// keys we actually read, and passes everything else through untouched.
|