cortico 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { OutputItem, Response, StreamEvent } from './index.ts';
|
|
2
|
+
import { STREAM_EVENT_TYPES } from './generated.ts';
|
|
3
|
+
|
|
4
|
+
export class ResponseProtocolError extends Error {
|
|
5
|
+
constructor(message: string) { super(message); this.name = 'ResponseProtocolError'; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Part = { type: string; text?: string; refusal?: string; annotations?: unknown[] };
|
|
9
|
+
type MutableItem = { id: string; type: string; status?: string; call_id?: string; name?: string; arguments?: string; content?: Part[]; summary?: Part[] };
|
|
10
|
+
|
|
11
|
+
function check(condition: unknown, message: string): asserts condition {
|
|
12
|
+
if (!condition) throw new ResponseProtocolError(message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function canonical(value: unknown): string {
|
|
16
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
17
|
+
if (value !== null && typeof value === 'object') {
|
|
18
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(',')}}`;
|
|
19
|
+
}
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One attempt's standard event stream. Transport termination is checked by finish(). */
|
|
24
|
+
export class ResponseAccumulator {
|
|
25
|
+
private response: Response | null = null;
|
|
26
|
+
private readonly items = new Map<number, OutputItem>();
|
|
27
|
+
private readonly ids = new Set<string>();
|
|
28
|
+
private readonly closed = new Set<number>();
|
|
29
|
+
private readonly closedParts = new Set<string>();
|
|
30
|
+
private lastSequence = -1;
|
|
31
|
+
private terminal = false;
|
|
32
|
+
|
|
33
|
+
accept(event: StreamEvent): void {
|
|
34
|
+
check(event && STREAM_EVENT_TYPES.has(event.type), 'Unknown Open Responses event type');
|
|
35
|
+
check(!this.terminal, 'Event after terminal response');
|
|
36
|
+
check(Number.isInteger(event.sequence_number) && event.sequence_number > this.lastSequence, 'Non-increasing event sequence');
|
|
37
|
+
this.lastSequence = event.sequence_number;
|
|
38
|
+
if (event.type === 'response.created') {
|
|
39
|
+
check(this.response === null, 'Duplicate response.created');
|
|
40
|
+
this.response = structuredClone(event.response);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
check(this.response !== null, 'Event before response.created');
|
|
44
|
+
if ('response' in event) {
|
|
45
|
+
check(event.response.id === this.response.id, 'Response identity changed');
|
|
46
|
+
const terminal = ['response.completed', 'response.incomplete', 'response.failed'].includes(event.type);
|
|
47
|
+
if (terminal) {
|
|
48
|
+
check(event.response.status === event.type.slice('response.'.length), 'Response terminal status disagrees with event');
|
|
49
|
+
check(event.response.output.length === this.items.size, 'Terminal response contains unannounced output items');
|
|
50
|
+
for (const [index, item] of this.items) {
|
|
51
|
+
const final = event.response.output[index];
|
|
52
|
+
check(final?.id === item.id, 'Terminal response changed output order');
|
|
53
|
+
if (this.closed.has(index)) check(canonical(item) === canonical(final), 'Terminal response changed a closed item');
|
|
54
|
+
}
|
|
55
|
+
this.terminal = true;
|
|
56
|
+
}
|
|
57
|
+
this.response = structuredClone(event.response);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (event.type === 'error') return;
|
|
61
|
+
if (event.type === 'response.output_item.added') {
|
|
62
|
+
check(event.item !== null, 'Missing output item');
|
|
63
|
+
check(Number.isInteger(event.output_index) && event.output_index >= 0, 'Invalid output index');
|
|
64
|
+
check(!this.items.has(event.output_index) && !this.ids.has(event.item.id), 'Duplicate output item');
|
|
65
|
+
this.items.set(event.output_index, structuredClone(event.item));
|
|
66
|
+
this.ids.add(event.item.id);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const item = this.items.get(event.output_index) as MutableItem | undefined;
|
|
70
|
+
check(item, 'Event refers to an unknown output item');
|
|
71
|
+
check(!this.closed.has(event.output_index), 'Update after output_item.done');
|
|
72
|
+
if (event.type === 'response.output_item.done') {
|
|
73
|
+
check(event.item !== null, 'Missing output item');
|
|
74
|
+
check(event.item.id === item.id && event.item.type === item.type, 'Item identity changed');
|
|
75
|
+
if (item.type === 'function_call') {
|
|
76
|
+
check(event.item.type === 'function_call' && event.item.arguments === item.arguments, 'Final function arguments disagree with deltas');
|
|
77
|
+
check(event.item.call_id === item.call_id && event.item.name === item.name, 'Final function identity changed');
|
|
78
|
+
}
|
|
79
|
+
for (const field of ['content', 'summary'] as const) {
|
|
80
|
+
if (item[field]?.length) check(canonical(item[field]) === canonical((event.item as MutableItem)[field]), `Final ${field} disagrees with deltas`);
|
|
81
|
+
}
|
|
82
|
+
check(!('status' in event.item) || event.item.status !== 'in_progress', 'Done item is still in progress');
|
|
83
|
+
this.items.set(event.output_index, structuredClone(event.item));
|
|
84
|
+
this.closed.add(event.output_index);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
check(event.item_id === item.id, 'Item ID disagrees with output index');
|
|
88
|
+
if (event.type === 'response.function_call_arguments.delta') {
|
|
89
|
+
check(item.type === 'function_call', 'Arguments on a non-function item');
|
|
90
|
+
check(!this.closedParts.has(`${item.id}/arguments`), 'Arguments after arguments.done');
|
|
91
|
+
item.arguments = (item.arguments ?? '') + event.delta;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (event.type === 'response.function_call_arguments.done') {
|
|
95
|
+
check(item.type === 'function_call' && item.arguments === event.arguments, 'Final function arguments disagree with deltas');
|
|
96
|
+
const key = `${item.id}/arguments`;
|
|
97
|
+
check(!this.closedParts.has(key), 'Duplicate arguments.done');
|
|
98
|
+
this.closedParts.add(key);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const summary = 'summary_index' in event;
|
|
102
|
+
const index = summary ? event.summary_index : 'content_index' in event ? event.content_index : -1;
|
|
103
|
+
check(Number.isInteger(index) && index >= 0, 'Invalid content index');
|
|
104
|
+
const parts = summary ? (item.summary ??= []) : (item.content ??= []);
|
|
105
|
+
const key = `${item.id}/${summary ? 'summary' : 'content'}/${index}`;
|
|
106
|
+
check(!this.closedParts.has(key), 'Update after content part completion');
|
|
107
|
+
if (event.type === 'response.content_part.added' || event.type === 'response.reasoning_summary_part.added') {
|
|
108
|
+
check(parts[index] === undefined, 'Duplicate content part');
|
|
109
|
+
parts[index] = structuredClone(event.part) as Part;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const part = parts[index];
|
|
113
|
+
check(part, 'Delta before content part');
|
|
114
|
+
if (event.type === 'response.content_part.done' || event.type === 'response.reasoning_summary_part.done') {
|
|
115
|
+
check(canonical(part) === canonical(event.part), 'Final content part disagrees with deltas');
|
|
116
|
+
this.closedParts.add(key);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (event.type === 'response.output_text.annotation.added') {
|
|
120
|
+
(part.annotations ??= []).push(structuredClone(event.annotation));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const field = event.type.startsWith('response.refusal.') ? 'refusal' : 'text';
|
|
124
|
+
const textKey = `${key}/${field}`;
|
|
125
|
+
if ('delta' in event) {
|
|
126
|
+
check(!this.closedParts.has(textKey), 'Text delta after text.done');
|
|
127
|
+
part[field] = (part[field] ?? '') + event.delta;
|
|
128
|
+
} else {
|
|
129
|
+
const final = 'text' in event ? event.text : 'refusal' in event ? event.refusal : undefined;
|
|
130
|
+
check(final !== undefined && part[field] === final, 'Final text disagrees with deltas');
|
|
131
|
+
check(!this.closedParts.has(textKey), 'Duplicate text.done');
|
|
132
|
+
this.closedParts.add(textKey);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
snapshot(): Response | null {
|
|
137
|
+
if (!this.response) return null;
|
|
138
|
+
if (this.terminal) return structuredClone(this.response);
|
|
139
|
+
return { ...structuredClone(this.response), output: [...this.items.entries()].sort(([a], [b]) => a - b).map(([, item]) => structuredClone(item)) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
finish(): Response {
|
|
143
|
+
check(this.terminal, 'Transport ended before a terminal response');
|
|
144
|
+
return this.snapshot()!;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { itemText, type ContextRecord } from './context.ts';
|
|
2
|
+
|
|
3
|
+
/** Approximate context occupancy shared by runtime and console; excludes native wire overhead. */
|
|
4
|
+
export function estimateTokens(text: string): number {
|
|
5
|
+
let cjk = 0;
|
|
6
|
+
let other = 0;
|
|
7
|
+
for (const ch of text) {
|
|
8
|
+
const code = ch.codePointAt(0)!;
|
|
9
|
+
if (
|
|
10
|
+
(code >= 0x4e00 && code <= 0x9fff) ||
|
|
11
|
+
(code >= 0x3000 && code <= 0x30ff) ||
|
|
12
|
+
(code >= 0xff00 && code <= 0xffef)
|
|
13
|
+
)
|
|
14
|
+
cjk++;
|
|
15
|
+
else other++;
|
|
16
|
+
}
|
|
17
|
+
return Math.ceil(cjk * 0.6 + other * 0.3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function estimateMessagesTokens(messages: readonly ContextRecord[]): number {
|
|
21
|
+
return messages.reduce(
|
|
22
|
+
(total, { item }) =>
|
|
23
|
+
total +
|
|
24
|
+
8 +
|
|
25
|
+
estimateTokens(item.type === 'function_call' ? item.name + item.arguments : itemText(item)) +
|
|
26
|
+
(item.type === 'reasoning' && item.encrypted_content
|
|
27
|
+
? estimateTokens(item.encrypted_content)
|
|
28
|
+
: 0),
|
|
29
|
+
0,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!-- Owner: src/providers/base.ts, src/providers/registry.ts, src/providers/console/settings.ts, src/providers/console/config.ts, src/providers/openai-responses-compat/config.ts -->
|
|
2
|
+
|
|
3
|
+
# src/providers
|
|
4
|
+
|
|
5
|
+
`ProviderModule` 实现一种模型通信协议,`ProviderInstance` 对应一个端点,
|
|
6
|
+
`ProviderHost` 提供实例所需的配置、密钥、资源与日志接口。Core 通过 `ProviderRegistry.bind(name)`
|
|
7
|
+
获取带报价与来源信息的客户端,Persona 通过 Core 调用模型。
|
|
8
|
+
|
|
9
|
+
## 文件
|
|
10
|
+
|
|
11
|
+
| 文件 | 管什么 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `base.ts` | 三个接口与 `BaseProvider`(抽象 `respond`) |
|
|
14
|
+
| `registry.ts` | 目录扫描发现内建模块、`registerProviderModules()` 收扩展、实例缓存与 `bind()` |
|
|
15
|
+
| `configuration.ts` | `validateSpec` / `validateEntry`:外部配置校验 |
|
|
16
|
+
| `pricebook.ts` | 价目定义、快照、报价合并 |
|
|
17
|
+
| `console/` | provider 页的服务端:`ProviderSettings`(落盘、密钥、探活) |
|
|
18
|
+
| `openai-responses-compat/` | 内建模块:Responses 协议客户端与模型目录 |
|
|
19
|
+
| `llamacpp/` | 内建模块:llama-server 的 Chat 客户端、router 目录、官方 release 的下载安装与进程托管 |
|
|
20
|
+
| `transport/` | HTTP/SSE 引擎、Chat 与 Responses 请求转换、事件装配、计量、错误 |
|
|
21
|
+
|
|
22
|
+
## ProviderModule
|
|
23
|
+
|
|
24
|
+
`id` 必须等于目录名(扩展包里则是包声明的 id)。必填:`title`、`reasoningTiers`(空表 = 开放,
|
|
25
|
+
effort 收任意非空串)、`serviceTiers`、`create(name, entry, host)`。可选:`defaultBaseUrl` 与
|
|
26
|
+
`baseUrlSuggestions`、`effortSuggestions`、`temperatureNote`、`localize()`、`normalize()`、
|
|
27
|
+
`validateEntry()`、`validateModel()`、`accepts()`(多模态判定)、`config()` 与 `console()`
|
|
28
|
+
(附加配置组与面板)、`prices()`、`estimateTokens()`、`contextOverflow()`。
|
|
29
|
+
|
|
30
|
+
地址、密钥变量名与图像开关由 `console/config.ts` 声明为 ConfigGroup。模块自己的
|
|
31
|
+
配置通过 `config()` 声明。模块可在 `instance` 插槽中渲染这些 schema,
|
|
32
|
+
通过 `ctx.setConfig` 保存;`console().config` 显式为空时省略独立配置页签,
|
|
33
|
+
配置组仍参与服务端校验。运行时启停和模型操作由面板 invoke 提供。
|
|
34
|
+
|
|
35
|
+
`create()` 返回 `ProviderInstance`:`client`(实现 `respond`)、`listModels?`、`control?`、
|
|
36
|
+
`compatibilityKey?`、`start?` / `stop?`、`contextWindow?(model)`。
|
|
37
|
+
|
|
38
|
+
`ProviderHost` 给实例:`stateDir`(`<部署根>/providers/<端点名>/`,归实例独占)、`repoRoot`、
|
|
39
|
+
`resource()`、`currentEntry()`、`secret(name)`(进程环境优先,否则读 `stateDir/.env` 一次)、
|
|
40
|
+
`readBlob()`、`keepThinking()`、`log`。
|
|
41
|
+
|
|
42
|
+
## 注册与解析
|
|
43
|
+
|
|
44
|
+
内建模块由 `providers/<module>/index.ts` 的默认导出自动发现。扩展模块由启动器在 `createBot`
|
|
45
|
+
之前通过 `registerProviderModules()` 注册,id 重复时抛错。
|
|
46
|
+
|
|
47
|
+
`ProviderRegistry.resolve(name)`:按 `entry.kind` 找模块,`normalize`,以去掉 `pricing` 与
|
|
48
|
+
`spec` 的条目 JSON 为缓存键(改模型或价格不重建实例),填 `stateDir` 与 `secret`。
|
|
49
|
+
`bind(name)` 在实例外包一层:注入 `quote`(报价快照)与 `origin`(实例、模块、模型、
|
|
50
|
+
`compatibilityDomain` = sha256(kind + baseUrl + `compatibilityKey()`))。Responses 历史推理仅在实例、
|
|
51
|
+
模块、兼容域与模型均匹配时回传。`invalidate()` 清除缓存;写入 `.env` 后必须调用,每个实例仅缓存一次文件内容。
|
|
52
|
+
|
|
53
|
+
## 配置形状
|
|
54
|
+
|
|
55
|
+
`LLMProviderEntry`:`kind`、`baseUrl`、`secret?`、`multimodal?`、`serviceTier?`、`pricing?`、
|
|
56
|
+
`options?`、`spec?: ModelSpec`。`ModelSpec`:`model`、`thinking`、`reasoningEffort?`、
|
|
57
|
+
`temperature?`、`maxTokens?`、`contextWindow?`。`providerSchemaVersion` 现在是 3。
|
|
58
|
+
|
|
59
|
+
Core 侧:`activeProviderEntry()` / `activeSpec()` 每次现读;`contextWindowOf()` 取上游探到值与
|
|
60
|
+
手填 `contextWindow` 的较小者;`contextFacts()` 优先用模块的 `estimateTokens` /
|
|
61
|
+
`contextOverflow`,没有则回落到 Core 的字符比估算。
|
|
62
|
+
|
|
63
|
+
## transport
|
|
64
|
+
|
|
65
|
+
`response-http.ts` 处理 HTTP/SSE:一次生成可包含多次请求尝试,重试间隔为 `[1s, 4s, 10s]`;超时
|
|
66
|
+
四档(流式首包 300s、非流式 120s、帧空闲 120s、内容空闲 300s);只对状态 0 / 429 / 5xx 重试,
|
|
67
|
+
401 / 403 先 `transport.refresh()` 一次;已提交不可逆增量后不再重试;输出字符超过
|
|
68
|
+
`max_output_tokens × 12` 时终止请求并报告超限;终态只接受 `completed` / `incomplete`,`failed` 抛
|
|
69
|
+
`LLMError`;每次请求尝试记录 `meters` 与 `charges`。
|
|
70
|
+
|
|
71
|
+
`responses-input.ts` 是原生 Responses 的无状态重放(system / developer 上提为
|
|
72
|
+
`instructions`);`chat.ts` + `native-input.ts` / `history.ts` 转换 Chat Completions 请求(历史
|
|
73
|
+
思维链不回传);`response-assembly.ts` 把两种流归一成 Open Responses 的 Item 流,
|
|
74
|
+
`finish_reason` 的 `length` / `content_filter` 落成 `incomplete_details.reason`;
|
|
75
|
+
`response-meters.ts` 把两种 usage 归一成 `TokenMeters`,缺项保持 null。
|
|
76
|
+
|
|
77
|
+
线协议类型在 `src/protocol/open-responses/`(规范 2026-04-24 生成),`ResponseAccumulator`
|
|
78
|
+
逐事件强校验:序号递增、终态自洽、已关闭的 Item 不得再变。
|
|
79
|
+
|
|
80
|
+
## pricebook
|
|
81
|
+
|
|
82
|
+
`PriceDefinition`:`models`、`currency`、`basis`(`marginal` | `equivalent`)、`rules`、
|
|
83
|
+
`inputBands`、`serviceTiers`、`source`。`quotePrices()` 在同一 basis 下让实例的 `pricing` 覆盖
|
|
84
|
+
模块默认,快照带 sha256 id 与 `capturedAt`。计量键:`input` / `output` / `total` /
|
|
85
|
+
`cachedInput` / `uncachedInput` / `reasoning` 与 `detail:*`,单价按每百万 token。实际扣费在
|
|
86
|
+
`src/core/generation.ts` 的 `priceUsage()`,缺计量记 `amount: null`。
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LLMProviderEntry,
|
|
3
|
+
Logger,
|
|
4
|
+
ReasoningTier,
|
|
5
|
+
ServiceTier,
|
|
6
|
+
ModelSpec,
|
|
7
|
+
ConfigGroup,
|
|
8
|
+
} from '../core/types.ts';
|
|
9
|
+
import type { ConsolePageContribution } from '../web/shared/console-protocol.ts';
|
|
10
|
+
import type { Language } from '../core/language.ts';
|
|
11
|
+
import type { ProviderConsoleHost } from './console/types.ts';
|
|
12
|
+
import type { PriceDefinition, QuoteTime } from './pricebook.ts';
|
|
13
|
+
import type { Request } from '../protocol/open-responses/index.ts';
|
|
14
|
+
import type { ContextRecord } from '../protocol/open-responses/context.ts';
|
|
15
|
+
import type { GenerateOptions, Generation, GenerationError, ResponseClient } from '../core/generation.ts';
|
|
16
|
+
|
|
17
|
+
/** Providers expose standard Responses and own their native transport semantics. */
|
|
18
|
+
export abstract class BaseProvider implements ResponseClient {
|
|
19
|
+
abstract respond(request: Request, options?: GenerateOptions): Promise<Generation>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ProviderHost {
|
|
23
|
+
/**
|
|
24
|
+
* 端点数据目录 <部署根>/providers/<端点名>/,内部结构由 provider 管理。
|
|
25
|
+
* 不同端点使用不同目录;同一部署根下共享该端点的部署共用目录。
|
|
26
|
+
*/
|
|
27
|
+
stateDir: string;
|
|
28
|
+
repoRoot?: string;
|
|
29
|
+
resource?<T>(key: string, create: () => T): T;
|
|
30
|
+
currentEntry?(): LLMProviderEntry;
|
|
31
|
+
/** 按名字取密钥:进程环境 > 这个端点的 `.env`。 */
|
|
32
|
+
secret(name: string): string;
|
|
33
|
+
/** 按句柄取回附件字节(渲染层把附件升格成内容分片时用);读不到 → null */
|
|
34
|
+
readBlob(handle: string): Buffer | null;
|
|
35
|
+
keepThinking(): boolean;
|
|
36
|
+
log: Logger;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 传给 ProviderRegistry 的宿主;registry 按端点补充 stateDir 和对应目录的 secret 读取器。 */
|
|
40
|
+
export type ProviderHostBase = Omit<ProviderHost, 'stateDir' | 'secret' | 'currentEntry' | 'resource'> & {
|
|
41
|
+
/** 同一部署根下共享的端点目录。 */
|
|
42
|
+
stateRoot: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface ProviderInstance {
|
|
46
|
+
client: BaseProvider;
|
|
47
|
+
/** Model ids the endpoint advertises (`GET /models`), with the context window when the catalog states one. */
|
|
48
|
+
listModels?(): Promise<Array<{ id: string; contextWindow?: number }>>;
|
|
49
|
+
/** Module-owned control object; interpreted by that module's console contribution. */
|
|
50
|
+
control?: unknown;
|
|
51
|
+
compatibilityKey?(): unknown;
|
|
52
|
+
start?(): Promise<unknown>;
|
|
53
|
+
stop?(): Promise<unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* Context window the upstream reports for this model, in tokens. How it is detected
|
|
56
|
+
* belongs to the module (a model catalog, a server properties endpoint, a launch
|
|
57
|
+
* parameter); `undefined` until known. The core takes the minimum of this and the
|
|
58
|
+
* profile's hand-filled `contextWindow`.
|
|
59
|
+
*/
|
|
60
|
+
contextWindow?(model: string): number | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 端点是否满足本地配置条件;不探测远端可达性或生成结果。 */
|
|
64
|
+
export interface ProviderAvailability {
|
|
65
|
+
ready: boolean;
|
|
66
|
+
/** 不可用的原因,控制台语言;可用时不带。 */
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ProviderModule {
|
|
71
|
+
id: string;
|
|
72
|
+
title: string;
|
|
73
|
+
defaultBaseUrl?: string;
|
|
74
|
+
/** Candidate endpoint URLs offered on the URL field. Candidates only: any URL is accepted. */
|
|
75
|
+
baseUrlSuggestions?: readonly string[];
|
|
76
|
+
/** Empty = open: `reasoningEffort` accepts any non-empty string; `effortSuggestions` supplies the candidates. */
|
|
77
|
+
reasoningTiers: readonly ReasoningTier[];
|
|
78
|
+
effortSuggestions?: readonly string[];
|
|
79
|
+
serviceTiers: readonly ServiceTier[];
|
|
80
|
+
temperatureNote?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Console copies of the tier tables and temperature note in the console language.
|
|
83
|
+
* Absent (or a field left out) = the tables above are shown as written.
|
|
84
|
+
*/
|
|
85
|
+
localize?(language: Language): {
|
|
86
|
+
reasoningTiers?: readonly ReasoningTier[];
|
|
87
|
+
serviceTiers?: readonly ServiceTier[];
|
|
88
|
+
temperatureNote?: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* 这个端点还缺什么本地条件。框架先查通用条件——模型、密钥——都齐了才问模块;
|
|
92
|
+
* 除此之外没有条件的模块不实现它。
|
|
93
|
+
*/
|
|
94
|
+
availability?(name: string, entry: LLMProviderEntry, language: Language): ProviderAvailability;
|
|
95
|
+
normalize?(entry: LLMProviderEntry): LLMProviderEntry;
|
|
96
|
+
/** `language` is the console language for the thrown message; validation itself is fixed. */
|
|
97
|
+
validateEntry?(entry: LLMProviderEntry, language: Language): void;
|
|
98
|
+
validateModel?(entry: LLMProviderEntry, spec: ModelSpec): void;
|
|
99
|
+
accepts?(entry: LLMProviderEntry, spec: ModelSpec, mime: string): boolean;
|
|
100
|
+
config?(name: string, entry: LLMProviderEntry, language: Language): ConfigGroup[];
|
|
101
|
+
console?(host: ProviderConsoleHost): Partial<ConsolePageContribution>;
|
|
102
|
+
prices?(entry: LLMProviderEntry, request: Request, at: QuoteTime): readonly PriceDefinition[];
|
|
103
|
+
/**
|
|
104
|
+
* Estimate tokens for records not yet covered by upstream usage.
|
|
105
|
+
* Absent: Core uses its character-ratio estimate.
|
|
106
|
+
*/
|
|
107
|
+
estimateTokens?(records: readonly ContextRecord[], spec: ModelSpec): number;
|
|
108
|
+
/** The upstream rejected a request because its input exceeded the model's context. */
|
|
109
|
+
contextOverflow?(error: GenerationError): boolean;
|
|
110
|
+
create(name: string, entry: LLMProviderEntry, host: ProviderHost): ProviderInstance;
|
|
111
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { LLMProviderEntry, ModelSpec } from '../core/types.ts';
|
|
2
|
+
import type { Language } from '../core/language.ts';
|
|
3
|
+
import type { ProviderAvailability, ProviderModule } from './base.ts';
|
|
4
|
+
import { validatePrices } from './pricebook.ts';
|
|
5
|
+
import { text } from './strings.ts';
|
|
6
|
+
|
|
7
|
+
/** `language` only picks the wording of the thrown message; the console passes its own. */
|
|
8
|
+
export function validateSpec(
|
|
9
|
+
module: ProviderModule,
|
|
10
|
+
entry: LLMProviderEntry,
|
|
11
|
+
value: unknown,
|
|
12
|
+
language: Language = 'zh',
|
|
13
|
+
): ModelSpec {
|
|
14
|
+
const S = text(language);
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
16
|
+
throw new Error(S.profileObject);
|
|
17
|
+
const spec = value as ModelSpec;
|
|
18
|
+
if (typeof spec.model !== 'string' || !spec.model.trim()) throw new Error(S.modelRequired);
|
|
19
|
+
if (typeof spec.thinking !== 'boolean') throw new Error(S.thinkingBoolean);
|
|
20
|
+
if (module.reasoningTiers.length) {
|
|
21
|
+
if (
|
|
22
|
+
!module.reasoningTiers.some(
|
|
23
|
+
(tier) => tier.thinking === spec.thinking && tier.effort === spec.reasoningEffort,
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
throw new Error(S.tierUnsupported(module.title));
|
|
27
|
+
} else if (spec.reasoningEffort !== undefined) {
|
|
28
|
+
if (typeof spec.reasoningEffort !== 'string' || !spec.reasoningEffort.trim())
|
|
29
|
+
throw new Error(S.effortString);
|
|
30
|
+
if (!spec.thinking) throw new Error(S.effortWithoutThinking);
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
spec.temperature !== undefined &&
|
|
34
|
+
(!Number.isFinite(spec.temperature) || spec.temperature < 0 || spec.temperature > 2)
|
|
35
|
+
)
|
|
36
|
+
throw new Error(S.temperatureRange);
|
|
37
|
+
for (const field of ['maxTokens', 'contextWindow'] as const) {
|
|
38
|
+
if (spec[field] !== undefined && (!Number.isInteger(spec[field]) || spec[field]! <= 0))
|
|
39
|
+
throw new Error(S.positiveInteger(field));
|
|
40
|
+
}
|
|
41
|
+
module.validateModel?.(entry, spec);
|
|
42
|
+
return structuredClone({
|
|
43
|
+
...spec,
|
|
44
|
+
model: spec.model.trim(),
|
|
45
|
+
...(spec.reasoningEffort !== undefined ? { reasoningEffort: spec.reasoningEffort.trim() } : {}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 返回第一项不满足的本地条件;通用条件满足后才询问模块。
|
|
51
|
+
*/
|
|
52
|
+
export function endpointAvailability(
|
|
53
|
+
module: ProviderModule,
|
|
54
|
+
name: string,
|
|
55
|
+
entry: LLMProviderEntry,
|
|
56
|
+
secretConfigured: boolean,
|
|
57
|
+
language: Language = 'zh',
|
|
58
|
+
): ProviderAvailability {
|
|
59
|
+
const S = text(language);
|
|
60
|
+
if (!entry.spec?.model) return { ready: false, reason: S.noModel };
|
|
61
|
+
if (entry.secret && !secretConfigured) return { ready: false, reason: S.noSecret(entry.secret) };
|
|
62
|
+
return module.availability?.(name, entry, language) ?? { ready: true };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateEntry(
|
|
66
|
+
module: ProviderModule,
|
|
67
|
+
value: LLMProviderEntry,
|
|
68
|
+
language: Language = 'zh',
|
|
69
|
+
): LLMProviderEntry {
|
|
70
|
+
const S = text(language);
|
|
71
|
+
if (
|
|
72
|
+
value.options !== undefined &&
|
|
73
|
+
(!value.options || typeof value.options !== 'object' || Array.isArray(value.options))
|
|
74
|
+
)
|
|
75
|
+
throw new Error(S.optionsObject);
|
|
76
|
+
const entry = module.normalize?.(structuredClone(value)) ?? structuredClone(value);
|
|
77
|
+
if (entry.kind !== module.id) throw new Error(S.kindChange);
|
|
78
|
+
const url = new URL(entry.baseUrl);
|
|
79
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password)
|
|
80
|
+
throw new Error(S.baseUrlFormat);
|
|
81
|
+
if (
|
|
82
|
+
entry.secret !== undefined &&
|
|
83
|
+
entry.secret !== '' &&
|
|
84
|
+
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(entry.secret)
|
|
85
|
+
)
|
|
86
|
+
throw new Error(S.secretName);
|
|
87
|
+
if (entry.multimodal !== undefined && typeof entry.multimodal !== 'boolean')
|
|
88
|
+
throw new Error(S.multimodalBoolean);
|
|
89
|
+
if (entry.serviceTier !== undefined && typeof entry.serviceTier !== 'string')
|
|
90
|
+
throw new Error(S.serviceTierString);
|
|
91
|
+
if (entry.serviceTier && !module.serviceTiers.some((tier) => tier.id === entry.serviceTier))
|
|
92
|
+
throw new Error(S.serviceTierUnsupported(module.title, entry.serviceTier));
|
|
93
|
+
if (entry.pricing !== undefined) entry.pricing = validatePrices(entry.pricing, language);
|
|
94
|
+
if (entry.spec !== undefined) entry.spec = validateSpec(module, entry, entry.spec, language);
|
|
95
|
+
module.validateEntry?.(entry, language);
|
|
96
|
+
return entry;
|
|
97
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ConfigGroup } from '../../core/config-schema.ts';
|
|
2
|
+
import type { LLMProviderEntry } from '../../core/types.ts';
|
|
3
|
+
import type { Language } from '../../core/language.ts';
|
|
4
|
+
import { text } from './strings.ts';
|
|
5
|
+
|
|
6
|
+
export function connectionGroup(name: string, entry: LLMProviderEntry, language: Language): ConfigGroup {
|
|
7
|
+
const S = text(language);
|
|
8
|
+
const prefix = `providers.${name}.`;
|
|
9
|
+
return {
|
|
10
|
+
id: `llm.${entry.kind}.${name}.connection`,
|
|
11
|
+
owner: `provider:${entry.kind}`,
|
|
12
|
+
schema: {
|
|
13
|
+
type: 'object', title: name, description: S.connectionDescription,
|
|
14
|
+
properties: {
|
|
15
|
+
[`${prefix}baseUrl`]: { type: 'string', title: S.baseUrl, 'x-hot': true },
|
|
16
|
+
[`${prefix}secret`]: { type: 'string', title: S.secret, description: S.secretDescription, 'x-hot': true },
|
|
17
|
+
[`${prefix}multimodal`]: { type: 'boolean', title: S.multimodal, 'x-hot': true },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|