@rol1n/pi-kit 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -58
- package/ask-u-questions/README.md +26 -0
- package/{extensions/ask-u-questions → ask-u-questions}/dialog-ui.ts +23 -0
- package/{extensions/ask-u-questions → ask-u-questions}/index.ts +9 -5
- package/{extensions/ask-u-questions → ask-u-questions}/multi-choice.ts +1 -3
- package/{extensions/ask-u-questions → ask-u-questions}/single-choice.ts +1 -3
- package/{extensions/ask-u-questions → ask-u-questions}/tool-renderers.ts +1 -3
- package/ask-u-questions/types.ts +192 -0
- package/continue-signal/README.md +20 -0
- package/{extensions/continue-signal → continue-signal}/index.ts +21 -3
- package/custom-providers/README.md +20 -0
- package/{extensions/custom-providers → custom-providers}/index.ts +16 -2
- package/image-generate/README.md +25 -0
- package/{extensions/image-generate → image-generate}/index.ts +22 -2
- package/{extensions/image-generate → image-generate}/model-selector.ts +1 -1
- package/model-vision/README.md +22 -0
- package/{extensions/multi-harness → multi-harness}/README.md +3 -3
- package/oai-subquota/README.md +22 -0
- package/{extensions/oai-subquota → oai-subquota}/index.ts +16 -6
- package/{extensions/oai-subquota → oai-subquota}/snapshot.ts +127 -1
- package/{extensions/oai-subquota → oai-subquota}/ui.ts +1 -1
- package/package.json +45 -46
- package/prompt-snipaste/README.md +27 -0
- package/{extensions/prompt-snipaste → prompt-snipaste}/renderers.ts +0 -1
- package/{extensions/prompt-snipaste → prompt-snipaste}/snippets.ts +5 -2
- package/{extensions/prompt-snipaste → prompt-snipaste}/types.ts +0 -1
- package/screenshot/README.md +14 -0
- package/srp-theme/README.md +18 -0
- package/{extensions/srp-theme → srp-theme}/footer.ts +0 -1
- package/{extensions/srp-theme → srp-theme}/tps-meter.ts +0 -1
- package/{extensions/subagent → subagent}/README.md +1 -1
- package/tui-asr/README.md +21 -0
- package/{extensions/tui-asr → tui-asr}/dictation.ts +70 -3
- package/{extensions/tui-asr → tui-asr}/index.ts +2 -2
- package/{extensions/tui-asr → tui-asr}/input.ts +30 -1
- package/{extensions/tui-asr → tui-asr}/model-selector.ts +1 -1
- package/web-reach/README.md +19 -0
- package/{extensions/web-reach → web-reach}/page.ts +84 -2
- package/windows-wheel-fix/README.md +12 -0
- package/extensions/ask-u-questions/README.md +0 -32
- package/extensions/ask-u-questions/answers.ts +0 -53
- package/extensions/ask-u-questions/results.ts +0 -83
- package/extensions/ask-u-questions/schema.ts +0 -17
- package/extensions/ask-u-questions/types.ts +0 -45
- package/extensions/ask-u-questions/ui-lock.ts +0 -24
- package/extensions/continue-signal/README.md +0 -28
- package/extensions/continue-signal/renderer.ts +0 -21
- package/extensions/custom-providers/README.md +0 -28
- package/extensions/image-generate/README.md +0 -29
- package/extensions/image-generate/models.ts +0 -19
- package/extensions/model-vision/README.md +0 -28
- package/extensions/oai-subquota/README.md +0 -30
- package/extensions/oai-subquota/api.ts +0 -27
- package/extensions/oai-subquota/auth.ts +0 -34
- package/extensions/oai-subquota/cache.ts +0 -30
- package/extensions/oai-subquota/types.ts +0 -40
- package/extensions/prompt-snipaste/README.md +0 -33
- package/extensions/screenshot/README.md +0 -25
- package/extensions/srp-theme/README.md +0 -24
- package/extensions/subagent/skills/subagent/SKILL.md +0 -70
- package/extensions/tui-asr/README.md +0 -28
- package/extensions/tui-asr/audio.ts +0 -40
- package/extensions/tui-asr/clipboard.ts +0 -30
- package/extensions/tui-asr/providers.ts +0 -25
- package/extensions/web-reach/README.md +0 -37
- package/extensions/web-reach/pdf.ts +0 -42
- package/extensions/web-reach/reader.ts +0 -42
- package/extensions/windows-wheel-fix/README.md +0 -21
- /package/{extensions/custom-providers → custom-providers}/asr/handlers/dashscope.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/asr/index.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/asr/models.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/asr/types.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/chat/index.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/chat/magpie.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/chat/models.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/classifier/index.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/classifier/models.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/image/handlers/dashscope.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/image/handlers/openrouter.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/image/index.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/image/models.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/image/types.ts +0 -0
- /package/{extensions/custom-providers → custom-providers}/types.ts +0 -0
- /package/{extensions/image-generate → image-generate}/files.ts +0 -0
- /package/{extensions/memory-log → memory-log}/README.md +0 -0
- /package/{extensions/memory-log → memory-log}/agent/consolidator/prompt.ts +0 -0
- /package/{extensions/memory-log → memory-log}/agent/consolidator/tools.ts +0 -0
- /package/{extensions/memory-log → memory-log}/agent/cost.ts +0 -0
- /package/{extensions/memory-log → memory-log}/agent/index.ts +0 -0
- /package/{extensions/memory-log → memory-log}/agent/observer/prompt.ts +0 -0
- /package/{extensions/memory-log → memory-log}/agent/observer/tool.ts +0 -0
- /package/{extensions/memory-log → memory-log}/commands/compact.ts +0 -0
- /package/{extensions/memory-log → memory-log}/commands/consolidate.ts +0 -0
- /package/{extensions/memory-log → memory-log}/commands/status.ts +0 -0
- /package/{extensions/memory-log → memory-log}/config.ts +0 -0
- /package/{extensions/memory-log → memory-log}/debug-log.ts +0 -0
- /package/{extensions/memory-log → memory-log}/hooks/compaction-hook.ts +0 -0
- /package/{extensions/memory-log → memory-log}/hooks/compaction-trigger.ts +0 -0
- /package/{extensions/memory-log → memory-log}/hooks/consolidator-trigger.ts +0 -0
- /package/{extensions/memory-log → memory-log}/hooks/observer-trigger.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ids.ts +0 -0
- /package/{extensions/memory-log → memory-log}/index.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/fold.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/index.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/pool.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/progress.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/projection.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/render.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/serialize.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ledger/types.ts +0 -0
- /package/{extensions/memory-log → memory-log}/memory/index-render.ts +0 -0
- /package/{extensions/memory-log → memory-log}/memory/paths.ts +0 -0
- /package/{extensions/memory-log → memory-log}/memory/session.ts +0 -0
- /package/{extensions/memory-log → memory-log}/runtime.ts +0 -0
- /package/{extensions/memory-log → memory-log}/spawn/launch.ts +0 -0
- /package/{extensions/memory-log → memory-log}/spawn/runs.ts +0 -0
- /package/{extensions/memory-log → memory-log}/tokens.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ui/status-controller.ts +0 -0
- /package/{extensions/memory-log → memory-log}/ui/timeline.ts +0 -0
- /package/{extensions/multi-harness → multi-harness}/adapters.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/harness-done.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/index.ts +0 -0
- /package/{extensions/multi-harness → multi-harness}/ipc.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/opencode-plugin.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/process-runner.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/turn-hook.mjs +0 -0
- /package/{extensions/multi-harness → multi-harness}/worker-bridge.ts +0 -0
- /package/{extensions/prompt-snipaste → prompt-snipaste}/index.ts +0 -0
- /package/{extensions/prompt-snipaste → prompt-snipaste}/menu.ts +0 -0
- /package/{prompts → prompt-snipaste/prompts}/ask-questions.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/delegate-exploration.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/diagnose-report.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/frontend-tech-preset.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/orchestrator-mode.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/session-kickoff.md +0 -0
- /package/{prompts → prompt-snipaste/prompts}/verify-not-assume.md +0 -0
- /package/{extensions/srp-theme → srp-theme}/index.ts +0 -0
- /package/{extensions/subagent → subagent}/bridge.ts +0 -0
- /package/{extensions/subagent → subagent}/index.ts +0 -0
- /package/{extensions/subagent → subagent}/mux.ts +0 -0
- /package/{extensions/subagent → subagent}/session.ts +0 -0
- /package/{extensions/subagent → subagent}/ui.ts +0 -0
- /package/{extensions/tui-asr → tui-asr}/status.ts +0 -0
- /package/{extensions/web-reach → web-reach}/html.ts +0 -0
- /package/{extensions/web-reach → web-reach}/index.ts +0 -0
- /package/{extensions/web-reach → web-reach}/rsc.ts +0 -0
- /package/{extensions/web-reach → web-reach}/search.ts +0 -0
- /package/{extensions/web-reach → web-reach}/transport.ts +0 -0
- /package/{extensions/windows-wheel-fix → windows-wheel-fix}/index.ts +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-kit
|
|
2
2
|
|
|
3
|
-
Pi extensions and prompt templates
|
|
3
|
+
Pi extensions and prompt templates distributed together as **`@rol1n/pi-kit`** and individually as **`@rol1n/<extension>`**. Each extension directory contains its own `index.ts` and `README.md`.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,92 +8,67 @@ Pi extensions and prompt templates, published together as **`@rol1n/pi-kit`** or
|
|
|
8
8
|
pi install npm:@rol1n/pi-kit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Install from GitHub or a local checkout
|
|
11
|
+
Install from GitHub or a local checkout:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
pi install
|
|
14
|
+
pi install https://github.com/RolinShmily/pi-kit
|
|
15
15
|
pi install /absolute/path/to/pi-kit
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Update
|
|
18
|
+
Update with `pi update npm:@rol1n/pi-kit` or `pi update --extensions`.
|
|
19
19
|
|
|
20
20
|
## Extensions
|
|
21
21
|
|
|
22
|
-
|
|
|
23
|
-
|
|
24
|
-
| [ask-u-questions](
|
|
25
|
-
| [continue-signal](
|
|
26
|
-
| [custom-providers](
|
|
27
|
-
| [image-generate](
|
|
28
|
-
| [memory-log](
|
|
29
|
-
| [model-vision](
|
|
30
|
-
| [multi-harness](
|
|
31
|
-
| [oai-subquota](
|
|
32
|
-
| [prompt-snipaste](
|
|
33
|
-
| [screenshot](
|
|
34
|
-
| [srp-theme](
|
|
35
|
-
| [subagent](
|
|
36
|
-
| [tui-asr](
|
|
37
|
-
| [web-reach](
|
|
38
|
-
| [windows-wheel-fix](
|
|
39
|
-
|
|
40
|
-
Independent package names use the `@rol1n/<name>` scope, such as `@rol1n/memory-log` or `@rol1n/multi-harness`. Each package has its own release version and README; the implementation is shared with the kit.
|
|
41
|
-
|
|
42
|
-
Every extension uses the same directory layout:
|
|
43
|
-
|
|
44
|
-
```text
|
|
45
|
-
extensions/<name>/
|
|
46
|
-
├── index.ts # Pi entrypoint and registration
|
|
47
|
-
├── README.md # Package documentation
|
|
48
|
-
└── ... # Modules grouped by responsibility
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
UI, transport, parsing, and storage helpers live beside the entrypoint as needed. Small extensions can remain a single cohesive `index.ts`.
|
|
22
|
+
| Extension | Purpose | Default in kit | Standalone npm |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| [ask-u-questions](ask-u-questions/README.md) | Interactive single/multi-choice and text dialogs | Yes | `@rol1n/ask-u-questions` |
|
|
25
|
+
| [continue-signal](continue-signal/README.md) | Resume an idle agent via `/continue` or Alt+C | Yes | `@rol1n/continue-signal` |
|
|
26
|
+
| [custom-providers](custom-providers/README.md) | Additional model providers and gateway registration | Yes | `@rol1n/custom-providers` |
|
|
27
|
+
| [image-generate](image-generate/README.md) | Image generation tool and `/image-generate` selector | Yes | `@rol1n/image-generate` |
|
|
28
|
+
| [memory-log](memory-log/README.md) | Background observational memory and compaction | Yes | `@rol1n/memory-log` |
|
|
29
|
+
| [model-vision](model-vision/README.md) | Image understanding and OCR tool | No | `@rol1n/model-vision` |
|
|
30
|
+
| [multi-harness](multi-harness/README.md) | Delegate to Pi, Codex, Claude, and OpenCode | Yes | `@rol1n/multi-harness` |
|
|
31
|
+
| [oai-subquota](oai-subquota/README.md) | OpenAI Codex subscription quota monitor | Yes | `@rol1n/oai-subquota` |
|
|
32
|
+
| [prompt-snipaste](prompt-snipaste/README.md) | Select prompt snippets for the next user message (bundles 7 built-in prompts) | Yes | `@rol1n/prompt-snipaste` |
|
|
33
|
+
| [screenshot](screenshot/README.md) | Insert the latest Windows/WSL screenshot path | No | `@rol1n/screenshot` |
|
|
34
|
+
| [srp-theme](srp-theme/README.md) | Prompt preview footer and streaming TPS meter | Yes | `@rol1n/srp-theme` |
|
|
35
|
+
| [subagent](subagent/README.md) | Delegate to asynchronous Pi workers | Yes | `@rol1n/subagent` |
|
|
36
|
+
| [tui-asr](tui-asr/README.md) | Streaming voice dictation via DashScope ASR | Yes | `@rol1n/tui-asr` |
|
|
37
|
+
| [web-reach](web-reach/README.md) | Keyless Exa web search and multi-tier page extraction | Yes | `@rol1n/web-reach` |
|
|
38
|
+
| [windows-wheel-fix](windows-wheel-fix/README.md) | Windows ConPTY/Zellij mouse-wheel reporting fix | Yes (Windows only) | `@rol1n/windows-wheel-fix` |
|
|
52
39
|
|
|
53
40
|
## Resource selection
|
|
54
41
|
|
|
55
|
-
The kit
|
|
56
|
-
|
|
57
|
-
Use `pi config` or narrow package resources in personal/project settings:
|
|
42
|
+
The kit exposes 13 extension entrypoints and the 7 prompt templates bundled in [prompt-snipaste/prompts](prompt-snipaste/prompts/). Narrow loaded resources with `pi config` or in `settings.json`:
|
|
58
43
|
|
|
59
44
|
```json
|
|
60
45
|
{
|
|
61
46
|
"packages": [
|
|
62
47
|
{
|
|
63
48
|
"source": "npm:@rol1n/pi-kit",
|
|
64
|
-
"extensions": ["
|
|
65
|
-
"prompts": ["prompts/diagnose-report.md"]
|
|
49
|
+
"extensions": ["**/*.ts", "!tui-asr/**"],
|
|
50
|
+
"prompts": ["prompt-snipaste/prompts/diagnose-report.md"]
|
|
66
51
|
}
|
|
67
52
|
]
|
|
68
53
|
}
|
|
69
54
|
```
|
|
70
55
|
|
|
71
|
-
Omit a resource field to load the package defaults; use `[]` to disable that resource type. Filters select resources already exposed by the package. When also installing an independent extension package, exclude its entry from the kit to avoid loading the same feature twice.
|
|
72
|
-
|
|
73
56
|
## Build and publish
|
|
74
57
|
|
|
75
|
-
|
|
58
|
+
[kit.config.json](kit.config.json) controls which extensions are bundled into `@rol1n/pi-kit` (`bundle: true`) and inter-extension npm dependencies (`deps`).
|
|
76
59
|
|
|
77
60
|
```sh
|
|
61
|
+
# Sync package.json from kit.config.json and validate
|
|
78
62
|
npm run manifest
|
|
79
63
|
npm run check
|
|
80
|
-
npm pack --dry-run --ignore-scripts
|
|
81
|
-
npm publish --access public
|
|
82
|
-
```
|
|
83
64
|
|
|
84
|
-
|
|
65
|
+
# Stage any extension into dist/<id>
|
|
66
|
+
npm run prepare:extension -- subagent
|
|
85
67
|
|
|
86
|
-
|
|
87
|
-
npm run
|
|
88
|
-
|
|
89
|
-
npm
|
|
90
|
-
npm publish --access public
|
|
68
|
+
# Publish all standalone extensions, or the kit, or one extension
|
|
69
|
+
npm run publish:ci -- extensions
|
|
70
|
+
npm run publish:ci -- kit
|
|
71
|
+
npm run publish:ci -- tui-asr
|
|
91
72
|
```
|
|
92
73
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
All packages link to [RolinShmily/pi-kit](https://github.com/RolinShmily/pi-kit). The `pi-package` keyword makes each published package eligible for the Pi gallery; indexing can take time. Pi supplies the host libraries declared as peer dependencies.
|
|
96
|
-
|
|
97
|
-
## Attribution
|
|
98
|
-
|
|
99
|
-
Existing author and copyright notices remain with their source files. Upstream issue references and integration details are linked from the individual extension READMEs, including the [Windows terminal workaround](extensions/windows-wheel-fix/README.md).
|
|
74
|
+
GitHub Actions OIDC Trusted Publishing is configured in [`.github/workflows/publish.yml`](.github/workflows/publish.yml).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ask-u-questions
|
|
2
|
+
|
|
3
|
+
Interactive single-choice, multiple-choice, and free-text questions for Pi. Registers the `ask_user_question` tool and serializes dialogs through a shared UI lock.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install npm:@rol1n/ask-u-questions
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also included in `@rol1n/pi-kit`.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"question": "Which output format should I use?",
|
|
18
|
+
"options": [
|
|
19
|
+
{ "label": "Markdown (Recommended)", "value": "markdown" },
|
|
20
|
+
{ "label": "JSON", "value": "json" }
|
|
21
|
+
],
|
|
22
|
+
"multiSelect": false
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Omit `options` or pass `[]` for free-text input. Set `multiSelect: true` only when multiple choices are expected. `details` adds supporting context, and an `Other` option is provided automatically for choice questions.
|
|
@@ -55,3 +55,26 @@ export function addWrapped(
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const SHARED_UI_LOCK_KEY = "__piSharedUiLock";
|
|
59
|
+
function getSharedUiLock(): { withLock<T>(fn: () => T | Promise<T>): Promise<T> } {
|
|
60
|
+
const g = globalThis as any;
|
|
61
|
+
if (!g[SHARED_UI_LOCK_KEY]) {
|
|
62
|
+
let chain: Promise<void> = Promise.resolve();
|
|
63
|
+
g[SHARED_UI_LOCK_KEY] = {
|
|
64
|
+
withLock<T>(fn: () => T | Promise<T>): Promise<T> {
|
|
65
|
+
const prev = chain;
|
|
66
|
+
let release: () => void;
|
|
67
|
+
chain = new Promise<void>((r) => {
|
|
68
|
+
release = r;
|
|
69
|
+
});
|
|
70
|
+
return prev.then(fn).finally(() => release!());
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return g[SHARED_UI_LOCK_KEY];
|
|
75
|
+
}
|
|
76
|
+
const sharedUiLock = getSharedUiLock();
|
|
77
|
+
|
|
78
|
+
export function withUILock<T>(fn: () => Promise<T>): Promise<T> {
|
|
79
|
+
return sharedUiLock.withLock(fn);
|
|
80
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/** Interactive questions, serialized through the shared dialog lock. */
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
AskUserQuestionParams,
|
|
5
|
+
buildResult,
|
|
6
|
+
cancelledResult,
|
|
7
|
+
normalizeOptions,
|
|
8
|
+
unavailableResult,
|
|
9
|
+
type AskUserQuestionMode,
|
|
10
|
+
} from "./types.ts";
|
|
11
|
+
import { withUILock } from "./dialog-ui.ts";
|
|
7
12
|
import { askSingleChoice } from "./single-choice.ts";
|
|
8
13
|
import { askMultiChoice } from "./multi-choice.ts";
|
|
9
|
-
import { withUILock } from "./ui-lock.ts";
|
|
10
14
|
import { questionRenderers } from "./tool-renderers.ts";
|
|
11
15
|
|
|
12
16
|
export type { AskOption, TextAnswer, OptionAnswer, OtherAnswer, AskAnswer, AskUserQuestionStatus, AskUserQuestionMode, AskUserQuestionResultDetails } from "./types.ts";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Editor, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import type
|
|
4
|
-
import { getOtherLabel, isChineseContext, sortAnswers } from "./answers.ts";
|
|
3
|
+
import { getOtherLabel, isChineseContext, sortAnswers, type AskAnswer, type AskOption, type DisplayOption } from "./types.ts";
|
|
5
4
|
import { addWrapped, createEditorTheme } from "./dialog-ui.ts";
|
|
6
5
|
|
|
7
6
|
export async function askMultiChoice(
|
|
@@ -257,4 +256,3 @@ export async function askMultiChoice(
|
|
|
257
256
|
},
|
|
258
257
|
);
|
|
259
258
|
}
|
|
260
|
-
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Editor, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import type
|
|
4
|
-
import { getOtherLabel, isChineseContext } from "./answers.ts";
|
|
3
|
+
import { getOtherLabel, isChineseContext, type AskAnswer, type AskOption, type DisplayOption } from "./types.ts";
|
|
5
4
|
import { addWrapped, createEditorTheme } from "./dialog-ui.ts";
|
|
6
5
|
|
|
7
6
|
export async function askSingleChoice(
|
|
@@ -179,4 +178,3 @@ export async function askSingleChoice(
|
|
|
179
178
|
},
|
|
180
179
|
);
|
|
181
180
|
}
|
|
182
|
-
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import
|
|
4
|
-
import type { AskUserQuestionResultDetails } from "./types.ts";
|
|
5
|
-
import { getOtherLabel, isChineseContext, normalizeOptions } from "./answers.ts";
|
|
3
|
+
import { AskUserQuestionParams, getOtherLabel, isChineseContext, normalizeOptions, type AskUserQuestionResultDetails } from "./types.ts";
|
|
6
4
|
|
|
7
5
|
export const questionRenderers: Pick<ToolDefinition<typeof AskUserQuestionParams, AskUserQuestionResultDetails>, "renderCall" | "renderResult"> = {
|
|
8
6
|
renderCall(args, theme, context) {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
|
|
3
|
+
const OptionSchema = Type.Object({
|
|
4
|
+
label: Type.String({ description: 'Option label. Put a recommended option first and append "(Recommended)".' }),
|
|
5
|
+
value: Type.Optional(Type.String({ description: "Machine-readable value; defaults to the label." })),
|
|
6
|
+
description: Type.Optional(Type.String({ description: "Additional context shown below the option." })),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const AskUserQuestionParams = Type.Object({
|
|
10
|
+
question: Type.String({ description: "The question to ask. Ask only one question per call." }),
|
|
11
|
+
details: Type.Optional(Type.String({ description: "Background or instructions shown below the question." })),
|
|
12
|
+
options: Type.Optional(Type.Array(OptionSchema, {
|
|
13
|
+
description: "Choices for selection. Omit or pass an empty array for free-text input. An Other choice is always available.",
|
|
14
|
+
})),
|
|
15
|
+
multiSelect: Type.Optional(Type.Boolean({ description: "Allow selecting multiple options when true." })),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export interface AskOption {
|
|
19
|
+
label: string;
|
|
20
|
+
value: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DisplayOption extends AskOption {
|
|
25
|
+
id: string;
|
|
26
|
+
index?: number;
|
|
27
|
+
isOther?: boolean;
|
|
28
|
+
isSubmit?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TextAnswer {
|
|
32
|
+
type: "text";
|
|
33
|
+
label: string;
|
|
34
|
+
value: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OptionAnswer {
|
|
38
|
+
type: "option";
|
|
39
|
+
label: string;
|
|
40
|
+
value: string;
|
|
41
|
+
index: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface OtherAnswer {
|
|
45
|
+
type: "other";
|
|
46
|
+
label: string;
|
|
47
|
+
value: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type AskAnswer = TextAnswer | OptionAnswer | OtherAnswer;
|
|
51
|
+
export type AskUserQuestionStatus = "answered" | "cancelled" | "unavailable";
|
|
52
|
+
export type AskUserQuestionMode = "text" | "single-select" | "multi-select";
|
|
53
|
+
|
|
54
|
+
export interface AskUserQuestionResultDetails {
|
|
55
|
+
status: AskUserQuestionStatus;
|
|
56
|
+
question: string;
|
|
57
|
+
context?: string;
|
|
58
|
+
mode: AskUserQuestionMode;
|
|
59
|
+
answers: AskAnswer[];
|
|
60
|
+
message?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function normalizeOptions(
|
|
64
|
+
options: Array<{ label: string; value?: string; description?: string }> | undefined,
|
|
65
|
+
): AskOption[] {
|
|
66
|
+
return (options || [])
|
|
67
|
+
.map((option) => ({
|
|
68
|
+
label: option.label.trim(),
|
|
69
|
+
value: option.value?.trim() || option.label.trim(),
|
|
70
|
+
description: option.description?.trim() || undefined,
|
|
71
|
+
}))
|
|
72
|
+
.filter((option) => option.label.length > 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isChineseContext(
|
|
76
|
+
question: string,
|
|
77
|
+
options?: Array<{ label?: string; description?: string }>,
|
|
78
|
+
context?: string,
|
|
79
|
+
): boolean {
|
|
80
|
+
if (/[\u4e00-\u9fa5]/.test(question)) return true;
|
|
81
|
+
if (context && /[\u4e00-\u9fa5]/.test(context)) return true;
|
|
82
|
+
if (options && options.some((o) => (o.label && /[\u4e00-\u9fa5]/.test(o.label)) || (o.description && /[\u4e00-\u9fa5]/.test(o.description)))) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getOtherLabel(options: AskOption[], isZh = false): string {
|
|
89
|
+
if (isZh) {
|
|
90
|
+
return options.some((option) => option.label === "其他(自定义输入)")
|
|
91
|
+
? "自定义输入"
|
|
92
|
+
: "其他(自定义输入)";
|
|
93
|
+
}
|
|
94
|
+
return options.some((option) => option.label === "Other (custom input)")
|
|
95
|
+
? "Custom input"
|
|
96
|
+
: "Other (custom input)";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function answerSortRank(answer: AskAnswer): number {
|
|
100
|
+
switch (answer.type) {
|
|
101
|
+
case "option":
|
|
102
|
+
return answer.index;
|
|
103
|
+
case "other":
|
|
104
|
+
return Number.MAX_SAFE_INTEGER - 1;
|
|
105
|
+
case "text":
|
|
106
|
+
return Number.MAX_SAFE_INTEGER;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function sortAnswers(answers: AskAnswer[]): AskAnswer[] {
|
|
111
|
+
return [...answers].sort((a, b) => answerSortRank(a) - answerSortRank(b));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatAnswerForModel(answer: AskAnswer): string {
|
|
115
|
+
switch (answer.type) {
|
|
116
|
+
case "text":
|
|
117
|
+
return answer.label;
|
|
118
|
+
case "other":
|
|
119
|
+
return `Other: ${answer.label}`;
|
|
120
|
+
case "option":
|
|
121
|
+
return `${answer.index}. ${answer.label}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildStructuredResult(
|
|
126
|
+
status: AskUserQuestionStatus,
|
|
127
|
+
question: string,
|
|
128
|
+
mode: AskUserQuestionMode,
|
|
129
|
+
answers: AskAnswer[],
|
|
130
|
+
context?: string,
|
|
131
|
+
message?: string,
|
|
132
|
+
): AskUserQuestionResultDetails {
|
|
133
|
+
return {
|
|
134
|
+
status,
|
|
135
|
+
question,
|
|
136
|
+
context,
|
|
137
|
+
mode,
|
|
138
|
+
answers,
|
|
139
|
+
message,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function cancelledResult(question: string, mode: AskUserQuestionMode, context?: string) {
|
|
144
|
+
const isZh = isChineseContext(question, undefined, context);
|
|
145
|
+
const message = isZh ? "用户取消了本次提问" : "User cancelled the question.";
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: "text" as const, text: message }],
|
|
148
|
+
details: buildStructuredResult("cancelled", question, mode, [], context, message),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function unavailableResult(question: string, mode: AskUserQuestionMode, message: string, context?: string) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text" as const, text: message }],
|
|
155
|
+
details: buildStructuredResult("unavailable", question, mode, [], context, message),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildResult(
|
|
160
|
+
question: string,
|
|
161
|
+
context: string | undefined,
|
|
162
|
+
mode: AskUserQuestionMode,
|
|
163
|
+
answers: AskAnswer[],
|
|
164
|
+
) {
|
|
165
|
+
const isZh = isChineseContext(question, answers, context);
|
|
166
|
+
let text: string;
|
|
167
|
+
if (mode === "text") {
|
|
168
|
+
const answer = answers[0];
|
|
169
|
+
if (isZh) {
|
|
170
|
+
text = answer.label.trim().length > 0
|
|
171
|
+
? `用户输入回答: ${answer.label}`
|
|
172
|
+
: "用户提交了空回答";
|
|
173
|
+
} else {
|
|
174
|
+
text = answer.label.trim().length > 0
|
|
175
|
+
? `User answered: ${answer.label}`
|
|
176
|
+
: "User submitted an empty answer";
|
|
177
|
+
}
|
|
178
|
+
} else if (mode === "single-select") {
|
|
179
|
+
text = isZh
|
|
180
|
+
? `用户选择项: ${formatAnswerForModel(answers[0])}`
|
|
181
|
+
: `User selected: ${formatAnswerForModel(answers[0])}`;
|
|
182
|
+
} else {
|
|
183
|
+
text = isZh
|
|
184
|
+
? `用户选择多项:\n${answers.map((answer) => `- ${formatAnswerForModel(answer)}`).join("\n")}`
|
|
185
|
+
: `User selected multiple:\n${answers.map((answer) => `- ${formatAnswerForModel(answer)}`).join("\n")}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "text" as const, text }],
|
|
190
|
+
details: buildStructuredResult("answered", question, mode, answers, context),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# continue-signal
|
|
2
|
+
|
|
3
|
+
Resume an idle Pi agent with a visible continuation signal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install npm:@rol1n/continue-signal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also included in `@rol1n/pi-kit`.
|
|
12
|
+
|
|
13
|
+
## Commands and shortcuts
|
|
14
|
+
|
|
15
|
+
| Action | Trigger |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Resume unfinished work | `/continue` or `Alt+C` |
|
|
18
|
+
| Resume with custom instruction | `/continue Review the remaining test failures` |
|
|
19
|
+
|
|
20
|
+
An explicit command argument takes priority; otherwise the current editor draft is consumed, falling back to a default resume prompt.
|
|
@@ -3,13 +3,31 @@
|
|
|
3
3
|
* Uses the editor draft when no prompt is supplied.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { Key } from "@earendil-works/pi-tui";
|
|
8
|
-
import { renderContinueSignal } from "./renderer.ts";
|
|
6
|
+
import type { ExtensionAPI, ExtensionContext, MessageRenderer } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Box, Key, Text } from "@earendil-works/pi-tui";
|
|
9
8
|
|
|
10
9
|
const DEFAULT_PROMPT = "Review the conversation context and resume any unfinished work from where it stopped.";
|
|
11
10
|
const CUSTOM_TYPE = "continue-signal";
|
|
12
11
|
|
|
12
|
+
const renderContinueSignal: MessageRenderer<{ timestamp?: number }> = (
|
|
13
|
+
message,
|
|
14
|
+
{ expanded, outputPad },
|
|
15
|
+
theme,
|
|
16
|
+
) => {
|
|
17
|
+
const time = new Date(message.details?.timestamp ?? Date.now()).toLocaleTimeString();
|
|
18
|
+
const title = `${theme.bold(theme.fg("accent", "⚡ LOOP RESUME SIGNAL"))} ${theme.fg("muted", `[${time}]` )}`;
|
|
19
|
+
|
|
20
|
+
const box = new Box(outputPad, 1, (text) => theme.bg("customMessageBg", text));
|
|
21
|
+
box.addChild(new Text(title, 0, 0));
|
|
22
|
+
box.addChild(new Text(theme.fg("text", "Re-linking conversation context & resuming agent loop..."), 0, 0));
|
|
23
|
+
|
|
24
|
+
if (expanded && message.content) {
|
|
25
|
+
box.addChild(new Text(theme.fg("dim", `Prompt: ${message.content}`), 0, 0));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return box;
|
|
29
|
+
};
|
|
30
|
+
|
|
13
31
|
export default function (pi: ExtensionAPI) {
|
|
14
32
|
pi.registerMessageRenderer(CUSTOM_TYPE, renderContinueSignal);
|
|
15
33
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# custom-providers
|
|
2
|
+
|
|
3
|
+
Registers additional model providers through Pi's native model registry and `/login` interface.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install npm:@rol1n/custom-providers
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also included in `@rol1n/pi-kit`.
|
|
12
|
+
|
|
13
|
+
## Providers
|
|
14
|
+
|
|
15
|
+
| Provider | Configuration |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Magpie | `MAGPIE_BASE_URL` (default `http://127.0.0.1:3425/v1`); authenticate via `MAGPIE_GATEWAY_KEY` or `/login magpie` |
|
|
18
|
+
| SHUAI API | Authenticate via `/login shuaiapi` |
|
|
19
|
+
| BigModel | Authenticate via `/login bigmodel` |
|
|
20
|
+
| DashScope | Authenticate via `/login dashscope`; `DASHSCOPE_IMAGE_URL` overrides the image API base URL |
|
|
@@ -10,9 +10,19 @@ import { imageProviders } from "./image/index.ts";
|
|
|
10
10
|
import { classifierProviders } from "./classifier/index.ts";
|
|
11
11
|
import { asrProviders } from "./asr/index.ts";
|
|
12
12
|
|
|
13
|
-
export { asrProviders, ALL_ASR_MODELS } from "./asr/index.ts";
|
|
13
|
+
export { asrProviders, ALL_ASR_MODELS, createAsrProvider, type AsrModelDefinition, type StreamingAsrProvider } from "./asr/index.ts";
|
|
14
|
+
export { imageProviders, fetchAllAvailableImageModels, generateImage, resolveImageModel, type ImageModelDefinition } from "./image/index.ts";
|
|
15
|
+
|
|
16
|
+
const BATCH_LOCK_KEY = "__piCustomProvidersBatchLock";
|
|
17
|
+
|
|
18
|
+
export function ensureCustomProviders(pi: ExtensionAPI): void {
|
|
19
|
+
const g = globalThis as Record<string, unknown>;
|
|
20
|
+
if (g[BATCH_LOCK_KEY]) return;
|
|
21
|
+
g[BATCH_LOCK_KEY] = true;
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
g[BATCH_LOCK_KEY] = false;
|
|
24
|
+
}, 0).unref?.();
|
|
14
25
|
|
|
15
|
-
export default function customProviders(pi: ExtensionAPI): void {
|
|
16
26
|
const providerIds = new Set<string>([
|
|
17
27
|
...Object.keys(chatProviders),
|
|
18
28
|
...Object.keys(imageProviders),
|
|
@@ -48,3 +58,7 @@ export default function customProviders(pi: ExtensionAPI): void {
|
|
|
48
58
|
pi.registerProvider(id, config);
|
|
49
59
|
}
|
|
50
60
|
}
|
|
61
|
+
|
|
62
|
+
export default function customProviders(pi: ExtensionAPI): void {
|
|
63
|
+
ensureCustomProviders(pi);
|
|
64
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# image-generate
|
|
2
|
+
|
|
3
|
+
Generate images through OpenRouter or DashScope and save them to `.pi/generated-images/` in the current project. Registers the `image_generate` tool and `/image-generate` model selector.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install npm:@rol1n/image-generate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also included in `@rol1n/pi-kit`. Installing this package automatically installs `@rol1n/custom-providers`.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
Run `/image-generate model` to open the interactive model picker, or `/image-generate model qwen-image-3.0-pro` to switch directly.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"prompt": "A clean technical diagram of a terminal workflow",
|
|
20
|
+
"aspectRatio": "16:9",
|
|
21
|
+
"model": "google/gemini-3.1-flash-image"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Authenticate via `/login openrouter` or `/login dashscope` (or `OPENROUTER_API_KEY` / `DASHSCOPE_API_KEY`).
|
|
@@ -12,13 +12,32 @@ import { Text, type AutocompleteItem } from "@earendil-works/pi-tui";
|
|
|
12
12
|
import { Type } from "typebox";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
+
ensureCustomProviders,
|
|
15
16
|
fetchAllAvailableImageModels,
|
|
16
17
|
generateImage,
|
|
17
18
|
resolveImageModel,
|
|
18
19
|
type ImageModelDefinition,
|
|
19
|
-
} from "../custom-providers/
|
|
20
|
+
} from "../custom-providers/index.ts";
|
|
20
21
|
import { saveImageAtomically } from "./files.ts";
|
|
21
|
-
|
|
22
|
+
/** Image catalog lookup and provider selection. */
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_MODEL = "google/gemini-3.1-flash-image";
|
|
25
|
+
|
|
26
|
+
/** Image models known to Pi: OpenRouter's built-in catalog plus our DashScope registration. */
|
|
27
|
+
export function registryImageModels(ctx: { modelRegistry: { getModelsOfType(type: string): readonly any[] } }) {
|
|
28
|
+
return ctx.modelRegistry.getModelsOfType("image") as readonly { id: string; name?: string; provider: string }[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Resolve which provider owns a model id, preferring the Pi registry over heuristics. */
|
|
32
|
+
export function resolveProviderId(
|
|
33
|
+
ctx: { modelRegistry: { getModelsOfType(type: string): readonly any[] } },
|
|
34
|
+
modelId: string,
|
|
35
|
+
): string {
|
|
36
|
+
const lower = modelId.toLowerCase();
|
|
37
|
+
const known = registryImageModels(ctx).find((m) => m.id.toLowerCase() === lower);
|
|
38
|
+
if (known) return known.provider;
|
|
39
|
+
return lower.startsWith("qwen-image") ? "dashscope" : "openrouter";
|
|
40
|
+
}
|
|
22
41
|
import { SearchableImageModelSelectorComponent } from "./model-selector.ts";
|
|
23
42
|
|
|
24
43
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -28,6 +47,7 @@ let currentModel = DEFAULT_MODEL;
|
|
|
28
47
|
// ============================ Extension Entrypoint ============================
|
|
29
48
|
|
|
30
49
|
export default function imageGenerate(pi: ExtensionAPI): void {
|
|
50
|
+
ensureCustomProviders(pi);
|
|
31
51
|
// 1. Slash command: /image-generate model [name]
|
|
32
52
|
pi.registerCommand("image-generate", {
|
|
33
53
|
description: "Switch default image generation model (/image-generate model [name])",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Container, Input, Spacer, Text, fuzzyFilter, getKeybindings } from "@earendil-works/pi-tui";
|
|
2
|
-
import type { ImageModelDefinition } from "../custom-providers/
|
|
2
|
+
import type { ImageModelDefinition } from "../custom-providers/index.ts";
|
|
3
3
|
|
|
4
4
|
export class SearchableImageModelSelectorComponent extends Container {
|
|
5
5
|
private searchInput: Input;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# model-vision
|
|
2
|
+
|
|
3
|
+
Analyze a local image or public HTTP(S) image URL with a vision model. Registers the `model_vision` tool for OCR, UI inspection, and visual Q&A.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install npm:@rol1n/model-vision
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Available as a standalone package (not enabled in `@rol1n/pi-kit` by default).
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"image": "./screenshots/error.png",
|
|
18
|
+
"prompt": "Read the error message and describe the relevant UI."
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Supports PNG, JPEG, WebP, GIF, and BMP up to 10 MiB. Private/local network URLs are blocked. Default model fallback chain is configured in `DEFAULT_VISION_MODELS` in `index.ts`.
|