@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21
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/CHANGELOG.md +292 -0
- package/README.md +8 -11
- package/docs/prefix-cache-architecture.md +14 -0
- package/package.json +22 -24
- package/packages/ask-user/README.md +55 -107
- package/packages/ask-user/ask-ui.ts +306 -732
- package/packages/ask-user/clipboard.ts +50 -0
- package/packages/ask-user/config.ts +74 -78
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/questions.ts +151 -0
- package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
- package/packages/ask-user/tools.ts +198 -432
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +38 -43
- package/packages/autocomplete/src/sorting.ts +8 -0
- package/packages/background-tasks/package.json +2 -2
- package/packages/background-tasks/src/config.ts +0 -73
- package/packages/background-tasks/src/index.ts +20 -16
- package/packages/background-tasks/src/registry.ts +1 -171
- package/packages/background-tasks/src/types.ts +0 -32
- package/packages/btw/README.md +13 -56
- package/packages/btw/extensions/btw.ts +403 -1796
- package/packages/btw/package.json +5 -2
- package/packages/btw/skills/btw/SKILL.md +7 -143
- package/packages/compactor/README.md +56 -287
- package/packages/compactor/package.json +5 -9
- package/packages/compactor/skills/compactor/SKILL.md +13 -20
- package/packages/compactor/src/card.ts +90 -0
- package/packages/compactor/src/commands/index.ts +73 -130
- package/packages/compactor/src/compaction/compact-args.ts +5 -3
- package/packages/compactor/src/compaction/filter-noise.ts +1 -1
- package/packages/compactor/src/compaction/hooks.ts +487 -311
- package/packages/compactor/src/compaction/source.ts +178 -0
- package/packages/compactor/src/compaction/summarize.ts +586 -52
- package/packages/compactor/src/config/manager.ts +130 -127
- package/packages/compactor/src/config/schema.ts +32 -48
- package/packages/compactor/src/index.ts +40 -431
- package/packages/compactor/src/info-screen.ts +44 -190
- package/packages/compactor/src/session/recall-blocks.ts +1 -1
- package/packages/compactor/src/stats.ts +76 -0
- package/packages/compactor/src/tools/context-budget.ts +17 -34
- package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
- package/packages/compactor/src/tools/register.ts +29 -269
- package/packages/compactor/src/types.ts +47 -186
- package/packages/core/README.md +25 -0
- package/packages/core/child-agent.ts +1 -0
- package/packages/core/command-echo.ts +63 -0
- package/packages/core/compaction-context.ts +41 -0
- package/packages/core/constants.ts +8 -38
- package/packages/core/events.ts +0 -53
- package/packages/core/fusion-status.ts +4 -0
- package/packages/core/global.d.ts +10 -0
- package/packages/core/harness-messages.ts +392 -0
- package/packages/core/index.ts +14 -0
- package/packages/core/kanboard-status.ts +49 -0
- package/packages/core/long-horizon-owner-status.ts +46 -0
- package/packages/core/model-cache.ts +81 -7
- package/packages/core/package.json +6 -3
- package/packages/core/src/attach/detect.ts +163 -0
- package/packages/core/src/child-agent/child-args.ts +31 -0
- package/packages/core/src/child-agent/delivery.ts +41 -0
- package/packages/core/src/child-agent/index.ts +5 -0
- package/packages/core/src/child-agent/readers.ts +203 -0
- package/packages/core/src/child-agent/runtime.ts +584 -0
- package/packages/core/src/evidence.ts +78 -0
- package/packages/core/src/hints/browser.ts +278 -0
- package/packages/core/src/hints/crab-data.ts +19 -0
- package/packages/core/src/hints/index.ts +865 -0
- package/packages/core/src/hints/lines.ts +607 -0
- package/packages/core/src/hints/store.ts +120 -0
- package/packages/core/src/jev/chatter.ts +24 -0
- package/packages/core/src/jev/settings.ts +177 -14
- package/packages/core/src/package-colors.ts +2 -0
- package/packages/core/src/settings/catalog.ts +39 -15
- package/packages/core/src/settings/engine.ts +48 -2
- package/packages/core/src/settings/hub.ts +83 -20
- package/packages/core/src/settings/open.ts +49 -0
- package/packages/core/src/settings/schema.ts +27 -7
- package/packages/core/src/tui/hub-kit.ts +18 -5
- package/packages/core/src/tui/kit.ts +198 -0
- package/packages/core/src/tui/progress.ts +73 -0
- package/packages/core/src/turn/arbiter.ts +290 -0
- package/packages/footer/README.md +4 -6
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/glance-editor.ts +19 -11
- package/packages/footer/src/help.ts +1 -1
- package/packages/footer/src/index.ts +36 -3
- package/packages/footer/src/presets.ts +3 -4
- package/packages/footer/src/rendering/icons.ts +0 -20
- package/packages/footer/src/segments/compactor.ts +52 -142
- package/packages/footer/src/segments/status-ext.ts +0 -1
- package/packages/fusion/README.md +14 -4
- package/packages/fusion/package.json +4 -3
- package/packages/fusion/src/child-args.ts +1 -0
- package/packages/fusion/src/index.ts +161 -8
- package/packages/fusion/src/pi-settings.ts +57 -0
- package/packages/fusion/src/picker.ts +186 -51
- package/packages/fusion/src/preset.ts +56 -2
- package/packages/fusion/src/prompts.ts +1 -1
- package/packages/fusion/src/sidekick-runtime.ts +1 -420
- package/packages/fusion/src/tools.ts +107 -128
- package/packages/fusion/src/transcript.ts +27 -85
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/README.md +18 -30
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/burst.ts +88 -0
- package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
- package/packages/input-shortcuts/src/cleared-input.ts +97 -0
- package/packages/input-shortcuts/src/index.ts +99 -193
- package/packages/input-shortcuts/src/last-response.ts +40 -0
- package/packages/input-shortcuts/src/registers.ts +8 -23
- package/packages/input-shortcuts/src/types.ts +4 -16
- package/packages/input-shortcuts/src/undo-redo.ts +59 -47
- package/packages/kanboard/README.md +51 -47
- package/packages/kanboard/index.ts +293 -55
- package/packages/kanboard/package.json +8 -8
- package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
- package/packages/kanboard/src/badges.ts +93 -0
- package/packages/kanboard/src/bin.ts +4 -2
- package/packages/kanboard/src/commands.ts +218 -84
- package/packages/kanboard/src/debug.ts +20 -0
- package/packages/kanboard/src/guard.ts +267 -109
- package/packages/kanboard/src/monitor.ts +330 -0
- package/packages/kanboard/src/notice-buffer.ts +74 -0
- package/packages/kanboard/src/progress.ts +27 -0
- package/packages/kanboard/src/reminders.ts +234 -0
- package/packages/kanboard/src/settings.ts +69 -15
- package/packages/kanboard/src/shapes.ts +5 -0
- package/packages/long-horizon/index.ts +190 -13
- package/packages/long-horizon/package.json +3 -3
- package/packages/long-horizon/src/commands.ts +103 -7
- package/packages/long-horizon/src/compaction-brief.ts +35 -0
- package/packages/long-horizon/src/engine/continuation.ts +84 -15
- package/packages/long-horizon/src/engine/goal-state.ts +64 -13
- package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
- package/packages/long-horizon/src/engine/ralph.ts +3 -2
- package/packages/long-horizon/src/engine/runaway.ts +68 -11
- package/packages/long-horizon/src/engine/verifier.ts +19 -0
- package/packages/long-horizon/src/gate.ts +37 -12
- package/packages/long-horizon/src/judge/resolve.ts +1 -0
- package/packages/long-horizon/src/owner.ts +35 -2
- package/packages/long-horizon/src/progress.ts +87 -0
- package/packages/long-horizon/src/prompts/goal.ts +1 -1
- package/packages/long-horizon/src/runtime.ts +172 -18
- package/packages/long-horizon/src/settings.ts +54 -39
- package/packages/long-horizon/src/tools/goal.ts +6 -0
- package/packages/long-horizon/src/tools/graph.ts +1 -1
- package/packages/long-horizon/src/tools/swarm.ts +3 -3
- package/packages/mcp/package.json +2 -2
- package/packages/memory/commands.ts +7 -2
- package/packages/memory/index.ts +210 -32
- package/packages/memory/package.json +3 -3
- package/packages/memory/save-session.ts +264 -0
- package/packages/memory/settings.ts +17 -0
- package/packages/memory/tools.ts +253 -203
- package/packages/notify/package.json +2 -2
- package/packages/skill-registry/README.md +53 -16
- package/packages/skill-registry/index.ts +230 -0
- package/packages/skill-registry/package.json +19 -8
- package/packages/skill-registry/src/editor.ts +206 -0
- package/packages/skill-registry/src/judge.ts +289 -0
- package/packages/skill-registry/src/registry.ts +87 -0
- package/packages/skill-registry/src/settings.ts +259 -0
- package/packages/skill-registry/src/vault.ts +80 -0
- package/packages/subagents/README.md +39 -119
- package/packages/subagents/package.json +6 -13
- package/packages/subagents/skills/subagents/SKILL.md +18 -42
- package/packages/subagents/src/agents.ts +213 -0
- package/packages/subagents/src/cards.ts +182 -0
- package/packages/subagents/src/index.ts +609 -1084
- package/packages/subagents/src/manager.ts +429 -0
- package/packages/subagents/src/profiles.ts +130 -0
- package/packages/subagents/src/transcript.ts +154 -0
- package/packages/subagents/src/ui.ts +389 -0
- package/packages/unipi/bundled.js +39756 -45569
- package/packages/unipi/index.ts +55 -10
- package/packages/updater/package.json +2 -2
- package/packages/utility/README.md +76 -69
- package/packages/utility/package.json +2 -2
- package/packages/utility/skills/image/SKILL.md +37 -0
- package/packages/utility/skills/utility/SKILL.md +17 -60
- package/packages/utility/src/answer/extract.ts +128 -0
- package/packages/utility/src/answer/index.ts +213 -0
- package/packages/utility/src/answer/reply.ts +133 -0
- package/packages/utility/src/answer/web.ts +167 -0
- package/packages/utility/src/attach/index.ts +205 -0
- package/packages/utility/src/commands.ts +38 -142
- package/packages/utility/src/diagnostics/engine.ts +50 -5
- package/packages/utility/src/image/index.ts +33 -0
- package/packages/utility/src/image/models.ts +221 -0
- package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
- package/packages/utility/src/image/settings.ts +178 -0
- package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
- package/packages/utility/src/image/tools.ts +168 -0
- package/packages/utility/src/image/transports.ts +186 -0
- package/packages/utility/src/index.ts +68 -389
- package/packages/utility/src/lifecycle/cleanup.ts +116 -311
- package/packages/utility/src/rename/gate.ts +102 -0
- package/packages/utility/src/rename/index.ts +191 -0
- package/packages/utility/src/rename/session.ts +170 -0
- package/packages/utility/src/render/delegated.ts +285 -0
- package/packages/utility/src/render/harness.ts +683 -0
- package/packages/utility/src/render/parse.ts +156 -0
- package/packages/utility/src/render/pi-settings.ts +38 -0
- package/packages/utility/src/render/reply-bg.ts +307 -0
- package/packages/utility/src/render/simple.ts +724 -0
- package/packages/utility/src/render/spacing.ts +235 -0
- package/packages/utility/src/render/styled.ts +251 -0
- package/packages/utility/src/render/tools.ts +353 -0
- package/packages/utility/src/settings.ts +166 -233
- package/packages/utility/src/types.ts +1 -222
- package/packages/watchdog/index.ts +17 -6
- package/packages/watchdog/package.json +3 -3
- package/packages/watchdog/src/config.ts +3 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/README.md +3 -0
- package/packages/workflow/index.ts +0 -2
- package/packages/workflow/package.json +2 -2
- package/packages/workflow/src/permission/decide.ts +6 -4
- package/packages/workflow/src/permission/index.ts +17 -7
- package/packages/workflow/src/permission/settings.ts +39 -4
- package/packages/workflow/src/plan/index.ts +19 -4
- package/packages/background-tasks/extensions/delegate-child.ts +0 -1
- package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
- package/packages/background-tasks/src/context-token-budget.ts +0 -903
- package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
- package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
- package/packages/background-tasks/src/delegate/budget.ts +0 -415
- package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
- package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
- package/packages/background-tasks/src/delegate/launch.ts +0 -484
- package/packages/background-tasks/src/delegate/result-package.ts +0 -459
- package/packages/background-tasks/src/delegate/runner.ts +0 -430
- package/packages/background-tasks/src/delegate/seed.ts +0 -423
- package/packages/background-tasks/src/delegate/types.ts +0 -323
- package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
- package/packages/background-tasks/src/delegate-extension.ts +0 -654
- package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
- package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
- package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
- package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
- package/packages/compactor/src/compaction/build-sections.ts +0 -76
- package/packages/compactor/src/compaction/extract/goals.ts +0 -36
- package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
- package/packages/compactor/src/compaction/format.ts +0 -53
- package/packages/compactor/src/compaction/merge.ts +0 -143
- package/packages/compactor/src/config/presets.ts +0 -160
- package/packages/compactor/src/executor/executor.ts +0 -233
- package/packages/compactor/src/executor/runtime.ts +0 -115
- package/packages/compactor/src/security/evaluator.ts +0 -89
- package/packages/compactor/src/security/policy.ts +0 -96
- package/packages/compactor/src/security/scanner.ts +0 -65
- package/packages/compactor/src/session/auto-inject.ts +0 -60
- package/packages/compactor/src/session/db.ts +0 -325
- package/packages/compactor/src/session/extract.ts +0 -144
- package/packages/compactor/src/session/resume-inject.ts +0 -64
- package/packages/compactor/src/session/snapshot.ts +0 -327
- package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
- package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
- package/packages/compactor/src/tools/ctx-execute.ts +0 -23
- package/packages/compactor/src/tools/ctx-stats.ts +0 -96
- package/packages/image/README.md +0 -99
- package/packages/image/package.json +0 -61
- package/packages/image/skills/image/SKILL.md +0 -76
- package/packages/image/src/generate.ts +0 -256
- package/packages/image/src/index.ts +0 -135
- package/packages/image/src/models.ts +0 -519
- package/packages/image/src/openai-images-api.ts +0 -282
- package/packages/image/src/register-providers.ts +0 -209
- package/packages/image/src/settings.ts +0 -173
- package/packages/image/src/tools.ts +0 -326
- package/packages/input-shortcuts/src/clipboard.ts +0 -119
- package/packages/kanboard/src/runner.ts +0 -817
- package/packages/skill-registry/skills/auto/SKILL.md +0 -465
- package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
- package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
- package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
- package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
- package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
- package/packages/skill-registry/skills/debug/SKILL.md +0 -228
- package/packages/skill-registry/skills/document/SKILL.md +0 -120
- package/packages/skill-registry/skills/fix/SKILL.md +0 -212
- package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
- package/packages/skill-registry/skills/plan/SKILL.md +0 -231
- package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
- package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
- package/packages/skill-registry/skills/research/SKILL.md +0 -251
- package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
- package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
- package/packages/skill-registry/skills/work/SKILL.md +0 -218
- package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
- package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
- package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
- package/packages/subagents/agents/delegate.md +0 -14
- package/packages/subagents/agents/oracle.md +0 -78
- package/packages/subagents/agents/researcher.md +0 -52
- package/packages/subagents/agents/reviewer.md +0 -80
- package/packages/subagents/agents/scout.md +0 -50
- package/packages/subagents/agents/worker.md +0 -59
- package/packages/subagents/prompts/council.md +0 -48
- package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
- package/packages/subagents/prompts/parallel-cleanup.md +0 -59
- package/packages/subagents/prompts/parallel-research.md +0 -50
- package/packages/subagents/prompts/parallel-review.md +0 -54
- package/packages/subagents/prompts/review-loop.md +0 -43
- package/packages/subagents/skills/council-mode/SKILL.md +0 -230
- package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
- package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
- package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
- package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
- package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
- package/packages/subagents/src/acceptance.ts +0 -331
- package/packages/subagents/src/agent-manager.ts +0 -405
- package/packages/subagents/src/agent-memory.ts +0 -300
- package/packages/subagents/src/agent-overrides.ts +0 -183
- package/packages/subagents/src/agent-runner.ts +0 -271
- package/packages/subagents/src/async-runner.ts +0 -422
- package/packages/subagents/src/authority-policy.ts +0 -47
- package/packages/subagents/src/budgets.ts +0 -285
- package/packages/subagents/src/child-safety.ts +0 -140
- package/packages/subagents/src/config.ts +0 -249
- package/packages/subagents/src/conversation-viewer.ts +0 -281
- package/packages/subagents/src/core-compat.ts +0 -94
- package/packages/subagents/src/custom-agents.ts +0 -343
- package/packages/subagents/src/file-system-retry.ts +0 -50
- package/packages/subagents/src/fleet-data.ts +0 -105
- package/packages/subagents/src/fleet-view.ts +0 -322
- package/packages/subagents/src/foreground-detach.ts +0 -59
- package/packages/subagents/src/fork-context.ts +0 -285
- package/packages/subagents/src/global.d.ts +0 -7
- package/packages/subagents/src/guide.ts +0 -129
- package/packages/subagents/src/mission-state.ts +0 -133
- package/packages/subagents/src/mission-store.ts +0 -436
- package/packages/subagents/src/output-limits.ts +0 -142
- package/packages/subagents/src/parity-types.ts +0 -315
- package/packages/subagents/src/pi-args.ts +0 -167
- package/packages/subagents/src/result-files.ts +0 -232
- package/packages/subagents/src/result-watcher.ts +0 -221
- package/packages/subagents/src/retained-children.ts +0 -176
- package/packages/subagents/src/run-fanout-budget.ts +0 -356
- package/packages/subagents/src/scheduled-runs.ts +0 -349
- package/packages/subagents/src/schemas.ts +0 -263
- package/packages/subagents/src/slash-commands.ts +0 -80
- package/packages/subagents/src/supervisor-channel.ts +0 -249
- package/packages/subagents/src/tool-handler.ts +0 -1144
- package/packages/subagents/src/types.ts +0 -238
- package/packages/subagents/src/widget.ts +0 -457
- package/packages/subagents/src/workflow-script.ts +0 -737
- package/packages/subagents/src/workflow-worker.ts +0 -384
- package/packages/subagents/src/worktree.ts +0 -614
- package/packages/utility/src/analytics/collector.ts +0 -293
- package/packages/utility/src/info-screen.ts +0 -80
- package/packages/utility/src/lifecycle/process.ts +0 -162
- package/packages/utility/src/prefix-cache.ts +0 -263
- package/packages/utility/src/skill-discovery.ts +0 -542
- package/packages/utility/src/tools/env.ts +0 -113
- package/packages/utility/src/tui/name-badge-state.ts +0 -315
- package/packages/utility/src/tui/name-badge.ts +0 -109
- /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/image — Model discovery and resolution
|
|
3
|
-
*
|
|
4
|
-
* Two different model families are involved:
|
|
5
|
-
*
|
|
6
|
-
* - **Image generation** uses pi-ai's `ImagesModel` catalog, which
|
|
7
|
-
* pi-coding-agent does not expose on `ExtensionContext` — it is reached
|
|
8
|
-
* through the pi-ai subpath exports.
|
|
9
|
-
* - **Image recognition** uses ordinary chat models filtered to those whose
|
|
10
|
-
* `input` modality includes `"image"` (`ctx.modelRegistry`).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/** A generation model, kept structural to avoid deep pi-ai type coupling. */
|
|
14
|
-
export interface ImageGenModel {
|
|
15
|
-
id: string;
|
|
16
|
-
name?: string;
|
|
17
|
-
provider: string;
|
|
18
|
-
api: string;
|
|
19
|
-
baseUrl?: string;
|
|
20
|
-
input?: string[];
|
|
21
|
-
output?: string[];
|
|
22
|
-
[key: string]: unknown;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** A vision-capable chat model. */
|
|
26
|
-
export interface VisionModel {
|
|
27
|
-
id: string;
|
|
28
|
-
name?: string;
|
|
29
|
-
provider: string;
|
|
30
|
-
input?: string[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Minimal chat-model registry surface (pi's ModelRegistry). */
|
|
34
|
-
export interface ChatModelRegistry {
|
|
35
|
-
find(provider: string, modelId: string): unknown;
|
|
36
|
-
getAll(): unknown[];
|
|
37
|
-
getAvailable?(): unknown[];
|
|
38
|
-
getApiKeyForProvider?(provider: string): Promise<string | undefined>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Split "provider/model-id" — the model id may itself contain slashes. */
|
|
42
|
-
export function splitModelRef(ref: string): { provider: string; id: string } | null {
|
|
43
|
-
const trimmed = ref.trim();
|
|
44
|
-
const slash = trimmed.indexOf("/");
|
|
45
|
-
if (slash <= 0 || slash === trimmed.length - 1) return null;
|
|
46
|
-
return { provider: trimmed.slice(0, slash), id: trimmed.slice(slash + 1) };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Format a model as "provider/model-id". */
|
|
50
|
-
export function formatModelRef(model: { provider: string; id: string }): string {
|
|
51
|
-
return `${model.provider}/${model.id}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* pi-ai's runtime image-model collection: model catalog, auth resolution and
|
|
56
|
-
* generation in one object. Only the parts used here are typed.
|
|
57
|
-
*/
|
|
58
|
-
export interface ImagesModelsLike {
|
|
59
|
-
getModels(provider?: string): readonly ImageGenModel[];
|
|
60
|
-
getModel(provider: string, id: string): ImageGenModel | undefined;
|
|
61
|
-
getAuth(model: ImageGenModel): Promise<{ apiKey?: string } | undefined>;
|
|
62
|
-
generateImages(
|
|
63
|
-
model: ImageGenModel,
|
|
64
|
-
context: { input: Array<{ type: string; text?: string }> },
|
|
65
|
-
options?: { apiKey?: string; signal?: AbortSignal },
|
|
66
|
-
): Promise<unknown>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let cachedImagesModels: ImagesModelsLike | null = null;
|
|
70
|
-
let imagesModelsAttempted = false;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Load pi-ai's built-in images collection.
|
|
74
|
-
*
|
|
75
|
-
* ⚠️ Do NOT reach for `getImageModels`/`getImageProviders`/`generateImages`:
|
|
76
|
-
*
|
|
77
|
-
* - They are NOT re-exported from the pi-ai package root. Importing
|
|
78
|
-
* `@earendil-works/pi-ai` and calling `getImageModels("openrouter")`
|
|
79
|
-
* returns an EMPTY ARRAY rather than throwing, so the mistake looks like
|
|
80
|
-
* "no models are installed" and costs a long debugging session.
|
|
81
|
-
* - The file that defines them, `dist/image-models.js`, is not a permitted
|
|
82
|
-
* subpath in pi-ai's `exports` map, so importing it directly throws
|
|
83
|
-
* ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
84
|
-
*
|
|
85
|
-
* The supported entry point is `@earendil-works/pi-ai/providers/all` →
|
|
86
|
-
* `builtinImagesModels()`, which returns the catalog AND resolves auth.
|
|
87
|
-
* A failure is not fatal: it degrades to null and callers report that no
|
|
88
|
-
* models are available.
|
|
89
|
-
*/
|
|
90
|
-
export async function getImagesModels(): Promise<ImagesModelsLike | null> {
|
|
91
|
-
if (cachedImagesModels || imagesModelsAttempted) return cachedImagesModels;
|
|
92
|
-
imagesModelsAttempted = true;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const mod = (await import("@earendil-works/pi-ai/providers/all")) as unknown as {
|
|
96
|
-
builtinImagesModels?: () => ImagesModelsLike;
|
|
97
|
-
};
|
|
98
|
-
if (typeof mod.builtinImagesModels === "function") {
|
|
99
|
-
cachedImagesModels = mod.builtinImagesModels();
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
cachedImagesModels = null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return cachedImagesModels;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Providers pi-ai's images collection can actually generate with.
|
|
110
|
-
*
|
|
111
|
-
* This is NOT the same set as pi's chat model registry: a chat provider
|
|
112
|
-
* registered by another extension may list image models that image generation
|
|
113
|
-
* cannot drive. Empty means "unknown", which callers treat as permissive.
|
|
114
|
-
*/
|
|
115
|
-
export async function getGeneratingProviders(): Promise<string[]> {
|
|
116
|
-
const images = await getImagesModels();
|
|
117
|
-
if (!images) return [];
|
|
118
|
-
try {
|
|
119
|
-
return [...new Set(images.getModels().map((m) => m.provider))];
|
|
120
|
-
} catch {
|
|
121
|
-
return [];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** List available image-generation models. Empty when unavailable. */
|
|
126
|
-
export async function listImageGenModels(): Promise<ImageGenModel[]> {
|
|
127
|
-
const images = await getImagesModels();
|
|
128
|
-
if (!images) return [];
|
|
129
|
-
try {
|
|
130
|
-
return [...images.getModels()];
|
|
131
|
-
} catch {
|
|
132
|
-
return [];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Heuristic: does a chat-registry model look like an image generator?
|
|
138
|
-
*
|
|
139
|
-
* Third-party providers registered by other extensions (pi-omniroute-bridge,
|
|
140
|
-
* for example) surface text-to-image endpoints as ordinary chat models. They
|
|
141
|
-
* declare no `output` modality at all, so a strict `output.includes("image")`
|
|
142
|
-
* check finds nothing and the user sees only pi-ai's built-in OpenRouter
|
|
143
|
-
* catalog. We therefore accept an explicit image output when present, and
|
|
144
|
-
* otherwise fall back to well-known generator naming.
|
|
145
|
-
*/
|
|
146
|
-
const GENERATOR_NAME_HINTS = [
|
|
147
|
-
"text-to-image",
|
|
148
|
-
"flux",
|
|
149
|
-
"dall-e",
|
|
150
|
-
"dalle",
|
|
151
|
-
"imagen",
|
|
152
|
-
"recraft",
|
|
153
|
-
"seedream",
|
|
154
|
-
"riverflow",
|
|
155
|
-
"grok-imagine",
|
|
156
|
-
"stable-diffusion",
|
|
157
|
-
"sdxl",
|
|
158
|
-
"midjourney",
|
|
159
|
-
"ideogram",
|
|
160
|
-
"nano-banana",
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
export function looksLikeImageGenerator(model: {
|
|
164
|
-
id: string;
|
|
165
|
-
name?: string;
|
|
166
|
-
output?: string[];
|
|
167
|
-
}): boolean {
|
|
168
|
-
// An explicit declaration always wins.
|
|
169
|
-
if (Array.isArray(model.output)) {
|
|
170
|
-
if (model.output.includes("image")) return true;
|
|
171
|
-
// Declared, but text-only — trust it and do not guess from the name.
|
|
172
|
-
if (model.output.length > 0) return false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
176
|
-
// Match "image" as a delimited segment ("gpt-5-image", "gemini-3-pro-image"),
|
|
177
|
-
// not the bare word anywhere — which would wrongly catch vision models such
|
|
178
|
-
// as "claude-image-understanding". \b handles the whitespace between id and
|
|
179
|
-
// name; a character class alone missed ids ending the id portion.
|
|
180
|
-
if (/(^|[/\-_\s])image\b/.test(haystack) && !haystack.includes("understand")) {
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
return GENERATOR_NAME_HINTS.some((hint) => haystack.includes(hint));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Image-generation models discovered from the chat registry — i.e. providers
|
|
188
|
-
* registered by other extensions, which pi-ai's built-in catalog knows nothing
|
|
189
|
-
* about.
|
|
190
|
-
*/
|
|
191
|
-
export function listRegistryImageGenModels(
|
|
192
|
-
registry: ChatModelRegistry,
|
|
193
|
-
): ImageGenModel[] {
|
|
194
|
-
let models: unknown[];
|
|
195
|
-
try {
|
|
196
|
-
models = registry.getAvailable?.() ?? registry.getAll();
|
|
197
|
-
} catch {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const out: ImageGenModel[] = [];
|
|
202
|
-
for (const model of models) {
|
|
203
|
-
if (model === null || typeof model !== "object") continue;
|
|
204
|
-
const candidate = model as Partial<ImageGenModel>;
|
|
205
|
-
if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if (!looksLikeImageGenerator(candidate as ImageGenModel)) continue;
|
|
209
|
-
out.push({
|
|
210
|
-
id: candidate.id,
|
|
211
|
-
provider: candidate.provider,
|
|
212
|
-
name: candidate.name,
|
|
213
|
-
api: candidate.api ?? "",
|
|
214
|
-
// Carry the endpoint through. The generic images adapter POSTs to
|
|
215
|
-
// `{baseUrl}/images/generations`, and this is the only place the
|
|
216
|
-
// registry's baseUrl is available — dropping it here surfaces later as
|
|
217
|
-
// "No baseUrl for image model ..." once generation is attempted.
|
|
218
|
-
...(candidate.baseUrl ? { baseUrl: candidate.baseUrl } : {}),
|
|
219
|
-
...(candidate.output ? { output: candidate.output } : {}),
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
return out;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Find a provider's API endpoint in pi's registry.
|
|
227
|
-
*
|
|
228
|
-
* Needed because a model can reach generation without one: a user-typed
|
|
229
|
-
* "provider/model-id" is accepted at face value by `asExplicitModelRef`, and
|
|
230
|
-
* carries no baseUrl of its own.
|
|
231
|
-
*/
|
|
232
|
-
export function findProviderBaseUrl(
|
|
233
|
-
registry: ChatModelRegistry | undefined,
|
|
234
|
-
provider: string,
|
|
235
|
-
): string | undefined {
|
|
236
|
-
if (!registry) return undefined;
|
|
237
|
-
try {
|
|
238
|
-
const models = (registry.getAvailable?.() ?? registry.getAll()) as Array<{
|
|
239
|
-
provider?: string;
|
|
240
|
-
baseUrl?: string;
|
|
241
|
-
}>;
|
|
242
|
-
return models.find((m) => m?.provider === provider && m.baseUrl)?.baseUrl;
|
|
243
|
-
} catch {
|
|
244
|
-
return undefined;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Every selectable generation model: pi-ai's built-in catalog plus anything
|
|
250
|
-
* contributed by registered providers, de-duplicated by "provider/id".
|
|
251
|
-
*/
|
|
252
|
-
export async function listAllImageGenModels(
|
|
253
|
-
registry?: ChatModelRegistry | null,
|
|
254
|
-
): Promise<ImageGenModel[]> {
|
|
255
|
-
const builtin = await listImageGenModels();
|
|
256
|
-
const fromRegistry = registry ? listRegistryImageGenModels(registry) : [];
|
|
257
|
-
|
|
258
|
-
const seen = new Set(builtin.map((m) => formatModelRef(m).toLowerCase()));
|
|
259
|
-
const merged = [...builtin];
|
|
260
|
-
for (const model of fromRegistry) {
|
|
261
|
-
const key = formatModelRef(model).toLowerCase();
|
|
262
|
-
if (seen.has(key)) continue;
|
|
263
|
-
seen.add(key);
|
|
264
|
-
merged.push(model);
|
|
265
|
-
}
|
|
266
|
-
return merged;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** Inject a stub images collection. Test-only. */
|
|
270
|
-
export function __setImagesModelsForTests(models: ImagesModelsLike | null): void {
|
|
271
|
-
cachedImagesModels = models;
|
|
272
|
-
imagesModelsAttempted = models !== null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/** Reset the model cache. Test-only. */
|
|
276
|
-
export function __resetModelCacheForTests(): void {
|
|
277
|
-
cachedImagesModels = null;
|
|
278
|
-
imagesModelsAttempted = false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Resolve a generation-model reference against the catalog.
|
|
283
|
-
*
|
|
284
|
-
* Exact "provider/id" first, then a scored fuzzy match so "flux" or
|
|
285
|
-
* "gemini-3-pro" work. Returns an error string (not a throw) so the tool can
|
|
286
|
-
* surface it as a normal tool error listing the alternatives.
|
|
287
|
-
*/
|
|
288
|
-
export function resolveImageGenModel(
|
|
289
|
-
input: string,
|
|
290
|
-
models: ImageGenModel[],
|
|
291
|
-
): ImageGenModel | string {
|
|
292
|
-
const raw = input.trim();
|
|
293
|
-
const query = raw.toLowerCase();
|
|
294
|
-
if (!query) return "No image model specified.";
|
|
295
|
-
if (models.length === 0) {
|
|
296
|
-
// A fully-qualified reference still works: detection is heuristic, so the
|
|
297
|
-
// user must be able to name a model we failed to discover.
|
|
298
|
-
const explicit = asExplicitModelRef(raw);
|
|
299
|
-
if (explicit) return explicit;
|
|
300
|
-
return (
|
|
301
|
-
"No image generation models are available.\n" +
|
|
302
|
-
"→ Image generation requires an OpenRouter account: https://openrouter.ai/keys\n" +
|
|
303
|
-
"→ Or set an exact model with /unipi:settings (press c to enter one manually)."
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// 1. Exact "provider/id"
|
|
308
|
-
const exact = models.find((m) => formatModelRef(m).toLowerCase() === query);
|
|
309
|
-
if (exact) return exact;
|
|
310
|
-
|
|
311
|
-
// 2. Exact id, ignoring the provider
|
|
312
|
-
const byId = models.find((m) => m.id.toLowerCase() === query);
|
|
313
|
-
if (byId) return byId;
|
|
314
|
-
|
|
315
|
-
// 3. Fuzzy
|
|
316
|
-
let best: ImageGenModel | undefined;
|
|
317
|
-
let bestScore = 0;
|
|
318
|
-
|
|
319
|
-
for (const model of models) {
|
|
320
|
-
const id = model.id.toLowerCase();
|
|
321
|
-
const full = formatModelRef(model).toLowerCase();
|
|
322
|
-
const name = (model.name ?? model.id).toLowerCase();
|
|
323
|
-
|
|
324
|
-
let score = 0;
|
|
325
|
-
if (id.includes(query) || full.includes(query)) {
|
|
326
|
-
score = 60 + (query.length / id.length) * 30;
|
|
327
|
-
} else if (name.includes(query)) {
|
|
328
|
-
score = 40 + (query.length / name.length) * 20;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (score > bestScore) {
|
|
332
|
-
bestScore = score;
|
|
333
|
-
best = model;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (best && bestScore > 0) return best;
|
|
338
|
-
|
|
339
|
-
// Nothing matched, but an explicit "provider/model-id" is taken at face
|
|
340
|
-
// value — the catalog is not authoritative for third-party providers.
|
|
341
|
-
const explicit = asExplicitModelRef(raw);
|
|
342
|
-
if (explicit) return explicit;
|
|
343
|
-
|
|
344
|
-
const sample = models.slice(0, 10).map((m) => ` ${formatModelRef(m)}`).join("\n");
|
|
345
|
-
return (
|
|
346
|
-
`Unknown image model "${input}".\n` +
|
|
347
|
-
`Available models (${models.length} total):\n${sample}` +
|
|
348
|
-
(models.length > 10 ? "\n …run /unipi:settings to browse all" : "")
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Treat a well-formed "provider/model-id" as a usable model even when it is
|
|
354
|
-
* absent from the catalog.
|
|
355
|
-
*
|
|
356
|
-
* Generator detection is heuristic and third-party providers publish no image
|
|
357
|
-
* metadata, so refusing an unknown-but-well-formed reference would make some
|
|
358
|
-
* models permanently unreachable. Requiring the provider segment keeps this
|
|
359
|
-
* from swallowing plain typos, which still get the "Unknown image model" list.
|
|
360
|
-
*/
|
|
361
|
-
function asExplicitModelRef(raw: string): ImageGenModel | null {
|
|
362
|
-
const parts = splitModelRef(raw);
|
|
363
|
-
if (!parts) return null;
|
|
364
|
-
if (/\s/.test(raw)) return null;
|
|
365
|
-
return { id: parts.id, provider: parts.provider, api: "" };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* List vision-capable chat models — those accepting image input.
|
|
370
|
-
*
|
|
371
|
-
* `Model.input` is `("text" | "image")[]` in pi-ai. Models that do not declare
|
|
372
|
-
* the field are excluded rather than assumed capable, so a bad guess never
|
|
373
|
-
* produces a confusing API error.
|
|
374
|
-
*/
|
|
375
|
-
export function listVisionModels(registry: ChatModelRegistry): VisionModel[] {
|
|
376
|
-
let models: unknown[];
|
|
377
|
-
try {
|
|
378
|
-
models = registry.getAvailable?.() ?? registry.getAll();
|
|
379
|
-
} catch {
|
|
380
|
-
return [];
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return models.filter(isVisionModel);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export function isVisionModel(model: unknown): model is VisionModel {
|
|
387
|
-
if (model === null || typeof model !== "object") return false;
|
|
388
|
-
const candidate = model as Partial<VisionModel>;
|
|
389
|
-
if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
return Array.isArray(candidate.input) && candidate.input.includes("image");
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Active tool names after hiding `recognizeTool` for a vision-capable model.
|
|
397
|
-
*
|
|
398
|
-
* A model that accepts image input can read images natively (pi's own read
|
|
399
|
-
* tool hands it the pixels), so a separate image_recognize round-trip through
|
|
400
|
-
* another model only duplicates the ability and burns system-prompt context.
|
|
401
|
-
* Text-only models get the tool back. Models that do not declare their input
|
|
402
|
-
* modalities are treated as non-vision, matching `isVisionModel`.
|
|
403
|
-
*/
|
|
404
|
-
export function applyRecognizeGating(
|
|
405
|
-
active: string[],
|
|
406
|
-
model: unknown,
|
|
407
|
-
recognizeTool: string,
|
|
408
|
-
): string[] {
|
|
409
|
-
const vision = isVisionModel(model);
|
|
410
|
-
const present = active.includes(recognizeTool);
|
|
411
|
-
// Already correct: hidden for a vision model, or provided for a text-only one.
|
|
412
|
-
if (vision !== present) return active;
|
|
413
|
-
return vision
|
|
414
|
-
? active.filter((name) => name !== recognizeTool)
|
|
415
|
-
: [...active, recognizeTool];
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Resolve a vision-model reference, restricted to image-capable models.
|
|
420
|
-
*
|
|
421
|
-
* Rejecting a text-only model here gives a much clearer message than letting
|
|
422
|
-
* the provider fail on an unexpected image part.
|
|
423
|
-
*/
|
|
424
|
-
export function resolveVisionModel(
|
|
425
|
-
input: string,
|
|
426
|
-
registry: ChatModelRegistry,
|
|
427
|
-
): VisionModel | string {
|
|
428
|
-
const vision = listVisionModels(registry);
|
|
429
|
-
const raw = input.trim();
|
|
430
|
-
|
|
431
|
-
if (vision.length === 0) {
|
|
432
|
-
// Accept an explicit reference so a provider we cannot introspect is still
|
|
433
|
-
// usable (mirrors resolveImageGenModel).
|
|
434
|
-
const explicit = asExplicitVisionRef(raw);
|
|
435
|
-
if (explicit) return explicit;
|
|
436
|
-
return (
|
|
437
|
-
"No vision-capable models are configured.\n" +
|
|
438
|
-
"→ image_recognize needs a model that accepts image input " +
|
|
439
|
-
"(e.g. anthropic/claude-sonnet, openai/gpt-5, google/gemini-3-pro).\n" +
|
|
440
|
-
"→ Configure one with /model or /unipi:settings."
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const query = raw.toLowerCase();
|
|
445
|
-
if (!query) return "No model specified.";
|
|
446
|
-
|
|
447
|
-
const exact = vision.find((m) => formatModelRef(m).toLowerCase() === query);
|
|
448
|
-
if (exact) return exact;
|
|
449
|
-
|
|
450
|
-
const byId = vision.find((m) => m.id.toLowerCase() === query);
|
|
451
|
-
if (byId) return byId;
|
|
452
|
-
|
|
453
|
-
let best: VisionModel | undefined;
|
|
454
|
-
let bestScore = 0;
|
|
455
|
-
|
|
456
|
-
for (const model of vision) {
|
|
457
|
-
const id = model.id.toLowerCase();
|
|
458
|
-
const full = formatModelRef(model).toLowerCase();
|
|
459
|
-
const name = (model.name ?? model.id).toLowerCase();
|
|
460
|
-
|
|
461
|
-
let score = 0;
|
|
462
|
-
if (id.includes(query) || full.includes(query)) {
|
|
463
|
-
score = 60 + (query.length / id.length) * 30;
|
|
464
|
-
} else if (name.includes(query)) {
|
|
465
|
-
score = 40 + (query.length / name.length) * 20;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (score > bestScore) {
|
|
469
|
-
bestScore = score;
|
|
470
|
-
best = model;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (best && bestScore > 0) return best;
|
|
475
|
-
|
|
476
|
-
// A known model that simply cannot see gets a targeted message.
|
|
477
|
-
let all: unknown[] = [];
|
|
478
|
-
try {
|
|
479
|
-
all = registry.getAvailable?.() ?? registry.getAll();
|
|
480
|
-
} catch {
|
|
481
|
-
all = [];
|
|
482
|
-
}
|
|
483
|
-
const knownButBlind = all.some((m) => {
|
|
484
|
-
const candidate = m as Partial<VisionModel>;
|
|
485
|
-
if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
return (
|
|
489
|
-
formatModelRef(candidate as VisionModel).toLowerCase() === query ||
|
|
490
|
-
candidate.id.toLowerCase() === query
|
|
491
|
-
);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
if (knownButBlind) {
|
|
495
|
-
return `Model "${input}" does not accept image input. Vision-capable models: ${vision
|
|
496
|
-
.slice(0, 5)
|
|
497
|
-
.map(formatModelRef)
|
|
498
|
-
.join(", ")}`;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Unknown to the registry, but well-formed — accept it. Checked after
|
|
502
|
-
// `knownButBlind` so a registered text-only model still gets the precise
|
|
503
|
-
// "does not accept image input" error rather than being waved through.
|
|
504
|
-
const explicit = asExplicitVisionRef(raw);
|
|
505
|
-
if (explicit) return explicit;
|
|
506
|
-
|
|
507
|
-
return (
|
|
508
|
-
`Unknown model "${input}".\n` +
|
|
509
|
-
`Vision-capable models: ${vision.map(formatModelRef).join(", ")}`
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/** Accept a well-formed "provider/model-id" the registry does not know. */
|
|
514
|
-
function asExplicitVisionRef(raw: string): VisionModel | null {
|
|
515
|
-
const parts = splitModelRef(raw);
|
|
516
|
-
if (!parts) return null;
|
|
517
|
-
if (/\s/.test(raw)) return null;
|
|
518
|
-
return { id: parts.id, provider: parts.provider, input: ["text", "image"] };
|
|
519
|
-
}
|