ompchamber 1.2.0 → 2.0.0
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 +131 -147
- package/dist/client/index.html +4 -2
- package/dist/client/static/css/index.5f630421b7.css +2 -0
- package/dist/client/static/js/77.0d81c74cf0.js +139 -0
- package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
- package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
- package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
- package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
- package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
- package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
- package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
- package/dist/client/static/js/async/e.93214d622b.js +79 -0
- package/dist/client/static/js/async/g.74ff36419f.js +2 -0
- package/dist/client/static/js/async/{m.2e1ea3db9e.js.LICENSE.txt → g.74ff36419f.js.LICENSE.txt} +5 -0
- package/dist/client/static/js/async/h1.393f066d09.js +1 -0
- package/dist/client/static/js/async/jl.874d352278.js +1 -0
- package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
- package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
- package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
- package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
- package/dist/client/static/js/async/px.fc6877924b.js +1 -0
- package/dist/client/static/js/async/q.33d6779f98.js +21 -0
- package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
- package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
- package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/x1.da8687159b.js +90 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.d99d829136.js +1 -0
- package/dist/client/static/js/index.c05535dbde.js +38 -0
- package/package.json +3 -3
- package/src/client/components/common/ComposerTextarea.tsx +9 -1
- package/src/client/components/common/MarkdownRenderer.tsx +50 -12
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
- package/src/client/components/settings/Modal.tsx +11 -2
- package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
- package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
- package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
- package/src/client/components/workspace/SearchPanel.tsx +16 -3
- package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
- package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
- package/src/client/components/workspace/btw-panel/index.tsx +129 -45
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
- package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
- package/src/client/components/workspace/editor/index.tsx +11 -2
- package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
- package/src/client/components/workspace/file-explorer/index.tsx +86 -16
- package/src/client/components/workspace/git-panel/index.tsx +60 -78
- package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
- package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
- package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
- package/src/client/data/theme/terminal.ts +50 -52
- package/src/client/hooks/chat/btw/index.ts +139 -18
- package/src/client/hooks/chat/btw/intercept.ts +8 -8
- package/src/client/hooks/chat/btw/mode.ts +111 -16
- package/src/client/hooks/chat/composer/index.ts +10 -4
- package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
- package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
- package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
- package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
- package/src/client/hooks/chat/timeline/index.ts +8 -20
- package/src/client/hooks/chat/timeline/queue.ts +66 -9
- package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
- package/src/client/hooks/chat/timeline/scroll.ts +93 -20
- package/src/client/hooks/chat/timeline/session-load.ts +7 -1
- package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
- package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
- package/src/client/hooks/settings/providers.ts +34 -3
- package/src/client/hooks/ui/open-file.ts +7 -1
- package/src/client/hooks/ui/theme.ts +82 -57
- package/src/client/hooks/workspace/file-tabs.ts +5 -1
- package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
- package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
- package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
- package/src/client/hooks/workspace/search-stream.ts +24 -5
- package/src/client/main.tsx +6 -0
- package/src/client/tailwind.css +19 -56
- package/src/server/lib/btw/child.server.ts +41 -0
- package/src/server/lib/btw/lifecycle.server.ts +254 -0
- package/src/server/lib/btw/prompt.ts +13 -5
- package/src/server/lib/btw/registry.server.ts +13 -4
- package/src/server/lib/btw/runtime.server.ts +149 -156
- package/src/server/lib/btw/schema.server.ts +27 -0
- package/src/server/lib/btw/service.server.ts +106 -5
- package/src/server/lib/btw/store.server.ts +57 -6
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/settings-store.test.ts +65 -0
- package/src/server/lib/db/settings-store.ts +25 -0
- package/src/server/lib/fs/atomic-write.ts +42 -4
- package/src/server/lib/fs/config-lock.ts +74 -0
- package/src/server/lib/fs/git-ignore.ts +55 -5
- package/src/server/lib/fs/ripgrep.ts +119 -0
- package/src/server/lib/lifecycle/identity.ts +10 -64
- package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
- package/src/server/lib/lifecycle/proc/index.ts +38 -0
- package/src/server/lib/lifecycle/proc/linux.ts +80 -0
- package/src/server/lib/lifecycle/proc/ps.ts +86 -0
- package/src/server/lib/lifecycle/proc/types.ts +33 -0
- package/src/server/lib/lifecycle/proc/win32.ts +53 -0
- package/src/server/lib/models/provider-registry.server.ts +2 -2
- package/src/server/lib/models/registry.server.ts +1 -1
- package/src/server/lib/omp/config/behavior.ts +17 -17
- package/src/server/lib/omp/config/disabled-providers.ts +84 -35
- package/src/server/lib/omp/config/document.ts +128 -0
- package/src/server/lib/omp/config/extensions.ts +11 -10
- package/src/server/lib/omp/config/mcp.ts +4 -57
- package/src/server/lib/omp/config/model-overrides.ts +123 -0
- package/src/server/lib/omp/config/models-config.ts +92 -0
- package/src/server/lib/omp/config/models-validation.ts +159 -0
- package/src/server/lib/omp/config/path-scoped.ts +74 -0
- package/src/server/lib/omp/config/providers.ts +241 -211
- package/src/server/lib/omp/config/roles.ts +6 -14
- package/src/server/lib/omp/core/auth-credentials.ts +1 -1
- package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
- package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
- package/src/server/lib/omp/rpc/manager.ts +21 -21
- package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
- package/src/server/lib/omp/rpc/session-commands.ts +17 -29
- package/src/server/lib/omp/rpc/session-registry.ts +5 -66
- package/src/server/lib/omp/rpc/web-state.ts +0 -6
- package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
- package/src/server/lib/omp/session/auto-title.server.ts +95 -20
- package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
- package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
- package/src/server/lib/queue/delivery-harness.ts +88 -0
- package/src/server/lib/queue/delivery.server.test.ts +78 -149
- package/src/server/lib/queue/delivery.server.ts +105 -13
- package/src/server/lib/queue/store.server.ts +30 -5
- package/src/server/lib/terminal/foreground.test.ts +164 -0
- package/src/server/lib/terminal/foreground.ts +34 -6
- package/src/server/lib/terminal/history.ts +8 -7
- package/src/server/plugins/compress.ts +33 -5
- package/src/server/plugins/ssr.ts +14 -7
- package/src/server/routes/btw/topics.ts +89 -2
- package/src/server/routes/fs/git.ts +5 -6
- package/src/server/routes/fs/search.ts +17 -26
- package/src/server/routes/sessions/queue.ts +17 -4
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/model-override.ts +69 -0
- package/src/server/routes/settings/provider-models.ts +35 -12
- package/src/server/routes/settings/providers.ts +31 -1
- package/src/server/routes/settings/root.ts +8 -3
- package/src/shared/lib/chat/btw/client.ts +34 -0
- package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
- package/src/shared/lib/code/shiki-themes.ts +9 -5
- package/src/shared/lib/fs/file-tree.ts +17 -12
- package/src/shared/lib/markdown/autolink.test.ts +32 -0
- package/src/shared/lib/markdown/autolink.ts +50 -0
- package/src/shared/lib/markdown/document-urls.test.ts +68 -0
- package/src/shared/lib/markdown/document-urls.ts +118 -0
- package/src/shared/lib/markdown/marked.ts +27 -15
- package/src/shared/lib/markdown/mermaid.ts +2 -2
- package/src/shared/lib/models/catalog.ts +1 -1
- package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
- package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
- package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
- package/src/shared/lib/settings/client.ts +10 -4
- package/src/shared/lib/settings/diff.test.ts +31 -0
- package/src/shared/lib/settings/diff.ts +37 -0
- package/src/shared/lib/theme/catalog.test.ts +168 -0
- package/src/shared/lib/theme/catalog.ts +98 -0
- package/src/shared/lib/theme/color.ts +88 -0
- package/src/shared/lib/theme/css.ts +70 -0
- package/src/shared/lib/theme/palettes/chamber.ts +61 -0
- package/src/shared/lib/theme/palettes/dark.ts +313 -0
- package/src/shared/lib/theme/palettes/light.ts +313 -0
- package/src/shared/types/btw.ts +83 -4
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +8 -1
- package/src/shared/types/settings/state.ts +10 -4
- package/src/shared/types/theme.ts +48 -0
- package/dist/client/static/css/index.b938f4a693.css +0 -2
- package/dist/client/static/js/77.8022a800b5.js +0 -139
- package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
- package/dist/client/static/js/async/66.86544ad741.js +0 -93
- package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
- package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
- package/dist/client/static/js/async/e.6443e739a2.js +0 -79
- package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
- package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
- package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
- package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
- package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
- package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
- package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
- package/dist/client/static/js/async/vt.20406a5604.js +0 -1
- package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
- package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
- package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
- package/dist/client/static/js/index.57b39ffa7f.js +0 -34
- package/src/client/components/workspace/btw-panel/Composer.tsx +0 -181
- package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
- /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.33d6779f98.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.57b39ffa7f.js.LICENSE.txt → index.c05535dbde.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validation of a `models.yml` document against the rules omp enforces on load
|
|
8
|
+
* (`validateProviderConfiguration` + `ModelsConfigSchema` in omp's
|
|
9
|
+
* `config/models-config.ts`).
|
|
10
|
+
*
|
|
11
|
+
* omp's failure mode is what makes this necessary: a schema violation anywhere
|
|
12
|
+
* in the file disables EVERY custom provider at once — `Warning: models.yml
|
|
13
|
+
* validation failed — custom providers disabled` — so one malformed model
|
|
14
|
+
* entry takes the whole registry down, not just its own provider. The chamber
|
|
15
|
+
* writes this file, so it must not be the one that produces an invalid one.
|
|
16
|
+
*
|
|
17
|
+
* Only the rules the chamber can actually violate are checked; unknown keys are
|
|
18
|
+
* ignored because omp's schema ignores them too.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { isRecord } from '@/shared/lib/util/guards';
|
|
22
|
+
|
|
23
|
+
/** `api` values omp's `ApiSchema` accepts. */
|
|
24
|
+
const API_VALUES = new Set([
|
|
25
|
+
'openai-completions',
|
|
26
|
+
'openai-responses',
|
|
27
|
+
'openai-codex-responses',
|
|
28
|
+
'azure-openai-responses',
|
|
29
|
+
'anthropic-messages',
|
|
30
|
+
'bedrock-converse-stream',
|
|
31
|
+
'google-generative-ai',
|
|
32
|
+
'google-gemini-cli',
|
|
33
|
+
'google-vertex',
|
|
34
|
+
'openrouter-decisions',
|
|
35
|
+
'typesafe',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/** Auth modes that make an `apiKey` optional (omp: `ProviderAuthSchema`). */
|
|
39
|
+
const KEYLESS_AUTH = new Set(['none', 'oauth']);
|
|
40
|
+
|
|
41
|
+
/** A finite number, which is what omp's `"number"` type means — `NaN` and `Infinity` are rejected. */
|
|
42
|
+
function isFiniteNumber(value: unknown): value is number {
|
|
43
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Positive finite number, or absent — the only shape omp accepts for a token limit. */
|
|
47
|
+
function isOptionalPositive(value: unknown): boolean {
|
|
48
|
+
return value === undefined || (isFiniteNumber(value) && value > 0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every reason the document would fail omp's load. Empty means omp will accept
|
|
53
|
+
* it; each message mirrors omp's own wording so a surfaced error reads the same
|
|
54
|
+
* as the warning omp would have printed.
|
|
55
|
+
*/
|
|
56
|
+
export function validateModelsDocument(doc: Record<string, unknown> | null | undefined): string[] {
|
|
57
|
+
const errors: string[] = [];
|
|
58
|
+
if (!doc) return errors;
|
|
59
|
+
const providers = doc.providers;
|
|
60
|
+
if (providers === undefined) return errors;
|
|
61
|
+
if (!isRecord(providers)) return ['providers must be a mapping'];
|
|
62
|
+
|
|
63
|
+
for (const [slug, value] of Object.entries(providers)) {
|
|
64
|
+
if (!isRecord(value)) {
|
|
65
|
+
errors.push(`Provider ${slug}: must be a mapping`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const providerApi = value.api;
|
|
69
|
+
if (providerApi !== undefined && !API_VALUES.has(String(providerApi))) {
|
|
70
|
+
errors.push(`Provider ${slug}: unknown "api" value ${JSON.stringify(providerApi)}`);
|
|
71
|
+
}
|
|
72
|
+
if (typeof value.baseUrl === 'string' && value.baseUrl.length === 0) {
|
|
73
|
+
errors.push(`Provider ${slug}: "baseUrl" must be a non-empty string`);
|
|
74
|
+
}
|
|
75
|
+
if (typeof value.apiKey === 'string' && value.apiKey.length === 0) {
|
|
76
|
+
errors.push(`Provider ${slug}: "apiKey" must be a non-empty string`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const models = value.models;
|
|
80
|
+
if (models !== undefined && !Array.isArray(models)) {
|
|
81
|
+
// omp: `models: must be an array (was an object)`. The map form is the
|
|
82
|
+
// most damaging mistake here — omp rejects the whole file, so it must
|
|
83
|
+
// never reach disk.
|
|
84
|
+
errors.push(`Provider ${slug}: "models" must be an array`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const modelList = Array.isArray(models) ? models : [];
|
|
88
|
+
|
|
89
|
+
if (modelList.length > 0) {
|
|
90
|
+
if (typeof value.baseUrl !== 'string' || value.baseUrl.length === 0) {
|
|
91
|
+
errors.push(`Provider ${slug}: "baseUrl" is required when defining custom models.`);
|
|
92
|
+
}
|
|
93
|
+
const auth = value.auth === undefined ? 'apiKey' : String(value.auth);
|
|
94
|
+
if (typeof value.apiKey !== 'string' && !KEYLESS_AUTH.has(auth)) {
|
|
95
|
+
errors.push(`Provider ${slug}: "apiKey" is required when defining custom models unless auth is "none" or "oauth".`);
|
|
96
|
+
}
|
|
97
|
+
} else if (!value.baseUrl && !value.apiKey && !value.discovery && value.auth !== 'none' && !value.modelOverrides) {
|
|
98
|
+
errors.push(`Provider ${slug}: must specify "baseUrl", "apiKey", "auth: none", "discovery", "modelOverrides", or "models"`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const [index, model] of modelList.entries()) {
|
|
102
|
+
if (!isRecord(model)) {
|
|
103
|
+
errors.push(`Provider ${slug}, model #${index}: must be a mapping`);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const id = model.id;
|
|
107
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
108
|
+
errors.push(`Provider ${slug}: model missing "id"`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (model.api !== undefined && !API_VALUES.has(String(model.api))) {
|
|
112
|
+
errors.push(`Provider ${slug}, model ${id}: unknown "api" value ${JSON.stringify(model.api)}`);
|
|
113
|
+
}
|
|
114
|
+
if (providerApi === undefined && model.api === undefined) {
|
|
115
|
+
errors.push(`Provider ${slug}, model ${id}: no "api" specified. Set at provider or model level.`);
|
|
116
|
+
}
|
|
117
|
+
if (!isOptionalPositive(model.contextWindow)) {
|
|
118
|
+
errors.push(`Provider ${slug}, model ${id}: invalid contextWindow`);
|
|
119
|
+
}
|
|
120
|
+
if (!isOptionalPositive(model.maxTokens)) {
|
|
121
|
+
errors.push(`Provider ${slug}, model ${id}: invalid maxTokens`);
|
|
122
|
+
}
|
|
123
|
+
const cost = model.cost;
|
|
124
|
+
if (cost !== undefined) {
|
|
125
|
+
if (!isRecord(cost)) {
|
|
126
|
+
errors.push(`Provider ${slug}, model ${id}: "cost" must be a mapping`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const field of ['input', 'output', 'cacheRead', 'cacheWrite']) {
|
|
130
|
+
if (!isFiniteNumber(cost[field])) {
|
|
131
|
+
errors.push(`Provider ${slug}, model ${id}: cost.${field} must be a number`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return errors;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Drops model fields omp would reject instead of letting them reach the file —
|
|
142
|
+
* a non-finite price is a value the provider listing produced, not user intent,
|
|
143
|
+
* so the honest repair is to omit the price and keep the model.
|
|
144
|
+
*/
|
|
145
|
+
export function sanitizeModelEntry(model: Record<string, unknown>): Record<string, unknown> {
|
|
146
|
+
const next: Record<string, unknown> = { ...model };
|
|
147
|
+
const cost = next.cost;
|
|
148
|
+
if (isRecord(cost)) {
|
|
149
|
+
const fields = ['input', 'output', 'cacheRead', 'cacheWrite'] as const;
|
|
150
|
+
if (fields.every((field) => isFiniteNumber(cost[field]))) {
|
|
151
|
+
next.cost = { input: cost.input, output: cost.output, cacheRead: cost.cacheRead, cacheWrite: cost.cacheWrite };
|
|
152
|
+
} else {
|
|
153
|
+
delete next.cost;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!isOptionalPositive(next.contextWindow)) delete next.contextWindow;
|
|
157
|
+
if (!isOptionalPositive(next.maxTokens)) delete next.maxTokens;
|
|
158
|
+
return next;
|
|
159
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Faithful port of omp's path-scoped array resolution
|
|
8
|
+
* (`resolvePathScopedStringArray` in omp's `config/settings.ts`).
|
|
9
|
+
*
|
|
10
|
+
* `disabledProviders`, `enabledProviders`, and `enabledModels` are the three
|
|
11
|
+
* settings omp treats as path-scoped: an entry may be a bare string, or an
|
|
12
|
+
* object carrying `path`/`paths`/`pathPrefix`/`pathPrefixes` plus the values to
|
|
13
|
+
* apply only under those prefixes. The chamber must not flatten that shape —
|
|
14
|
+
* dropping the objects loses the user's per-project policy — and must resolve
|
|
15
|
+
* it the same way omp does, or the provider settings would disagree with the
|
|
16
|
+
* model list omp serves.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { homedir } from 'os';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { isRecord } from '@/shared/lib/util/guards';
|
|
22
|
+
|
|
23
|
+
/** Prefix keys omp accepts on a path-scoped entry, in its own order. */
|
|
24
|
+
export const PREFIX_KEYS = ['path', 'paths', 'pathPrefix', 'pathPrefixes'] as const;
|
|
25
|
+
/** Value keys omp accepts on a path-scoped entry, in its own order. */
|
|
26
|
+
export const VALUE_KEYS = ['values', 'items', 'providers'] as const;
|
|
27
|
+
|
|
28
|
+
function stringsFromUnknown(value: unknown): string[] {
|
|
29
|
+
if (typeof value === 'string') return [value];
|
|
30
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `~/x` → `<home>/x`, matching omp's `expandTilde`. */
|
|
34
|
+
function expandTilde(value: string): string {
|
|
35
|
+
if (value === '~') return homedir();
|
|
36
|
+
return value.startsWith('~/') ? path.join(homedir(), value.slice(2)) : value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** True when `cwd` sits at or under `prefix` (omp's `pathMatchesPrefix`). */
|
|
40
|
+
function pathMatchesPrefix(cwd: string, prefix: string): boolean {
|
|
41
|
+
const relative = path.relative(path.resolve(expandTilde(prefix)), path.resolve(cwd));
|
|
42
|
+
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Flatten a path-scoped setting for one cwd. Bare strings always apply;
|
|
47
|
+
* object entries apply only when one of their prefixes contains `cwd`.
|
|
48
|
+
*/
|
|
49
|
+
export function resolvePathScopedSlugs(value: unknown, cwd: string): string[] {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
const resolved: string[] = [];
|
|
52
|
+
for (const entry of value) {
|
|
53
|
+
if (typeof entry === 'string') {
|
|
54
|
+
resolved.push(entry);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!isRecord(entry)) continue;
|
|
58
|
+
const prefixes = PREFIX_KEYS.flatMap((key) => stringsFromUnknown(entry[key]));
|
|
59
|
+
if (prefixes.length === 0 || !prefixes.some((prefix) => pathMatchesPrefix(cwd, prefix))) continue;
|
|
60
|
+
resolved.push(...VALUE_KEYS.flatMap((key) => stringsFromUnknown(entry[key])));
|
|
61
|
+
}
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** True when the entry is a path-scoped object rather than a bare slug. */
|
|
66
|
+
export function isPathScopedEntry(entry: unknown): boolean {
|
|
67
|
+
return isRecord(entry) && PREFIX_KEYS.some((key) => entry[key] !== undefined);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The slugs a path-scoped entry contributes, ignoring its prefixes. */
|
|
71
|
+
export function pathScopedEntryValues(entry: unknown): string[] {
|
|
72
|
+
if (!isRecord(entry)) return [];
|
|
73
|
+
return VALUE_KEYS.flatMap((key) => stringsFromUnknown(entry[key]));
|
|
74
|
+
}
|