@visual-z/xeno 0.8.4 → 0.9.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/CHANGELOG.md +8 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
package/src/core/theme.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme schema and validation. Pure: a file is read elsewhere, this module
|
|
3
|
-
* only turns JSON into a complete Theme plus named problems.
|
|
4
|
-
*
|
|
5
|
-
* A broken theme must still produce a usable Theme (the dark builtin).
|
|
6
|
-
* Throwing here would take the agent down the moment the user edited a
|
|
7
|
-
* comma, which is when they most need it (feat-theming design).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export const THEME_TOKENS = [
|
|
11
|
-
"text",
|
|
12
|
-
"thinkingText",
|
|
13
|
-
"toolTitle",
|
|
14
|
-
"toolOutput",
|
|
15
|
-
"error",
|
|
16
|
-
"toolDiffAdded",
|
|
17
|
-
"toolDiffRemoved",
|
|
18
|
-
"status",
|
|
19
|
-
"prompt",
|
|
20
|
-
"selectedBg",
|
|
21
|
-
"muted",
|
|
22
|
-
"warning",
|
|
23
|
-
"mdHeading",
|
|
24
|
-
"mdCode",
|
|
25
|
-
"mdLink",
|
|
26
|
-
"userMessageBg",
|
|
27
|
-
"toolPendingBg",
|
|
28
|
-
"toolSuccessBg",
|
|
29
|
-
"toolErrorBg",
|
|
30
|
-
"thinkingLow",
|
|
31
|
-
"thinkingMedium",
|
|
32
|
-
"thinkingHigh",
|
|
33
|
-
"thinkingXhigh",
|
|
34
|
-
"thinkingMax",
|
|
35
|
-
] as const;
|
|
36
|
-
|
|
37
|
-
export type ThemeToken = (typeof THEME_TOKENS)[number];
|
|
38
|
-
|
|
39
|
-
export type ThemeColors = { readonly [K in ThemeToken]: string } & Readonly<Record<string, string>>;
|
|
40
|
-
const PI_THEME_TOKENS: readonly string[] = ["background","accent","border","borderAccent","borderMuted","success","error","warning","muted","dim","text","thinkingText","scrollbarTrack","scrollbarThumb","searchMatchText","userMessageText","customMessageText","customMessageLabel","toolTitle","toolOutput","mdHeading","mdLink","mdLinkUrl","mdCode","mdCodeBlock","mdCodeBlockBorder","mdQuote","mdQuoteBorder","mdHr","mdListBullet","toolDiffAdded","toolDiffRemoved","toolDiffContext","syntaxComment","syntaxKeyword","syntaxFunction","syntaxVariable","syntaxString","syntaxNumber","syntaxType","syntaxOperator","syntaxPunctuation","thinkingOff","thinkingMinimal","thinkingLow","thinkingMedium","thinkingHigh","thinkingXhigh","thinkingMax","bashMode","selectedBg","searchMatchBg","userMessageBg","customMessageBg","toolPendingBg","toolSuccessBg","toolErrorBg"];
|
|
41
|
-
|
|
42
|
-
export interface Theme {
|
|
43
|
-
readonly name: string;
|
|
44
|
-
readonly colors: ThemeColors;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ThemeParse {
|
|
48
|
-
readonly theme: Theme;
|
|
49
|
-
readonly diagnostics: readonly string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Built-in appearances own no colours of their own: every token is one of the
|
|
54
|
-
* terminal's 16 slots or its default (empty), and no token paints a
|
|
55
|
-
* background (docs/tasks/refactor-tui-terminal-colors.md). The interactive
|
|
56
|
-
* view derives finer greys from the terminal's reported palette; these
|
|
57
|
-
* tokens are what line mode and an unreporting terminal get.
|
|
58
|
-
*
|
|
59
|
-
* `dark` and `light` differ only in which half of the palette stays legible:
|
|
60
|
-
* bright slots on a dark ground, normal slots on a light one. Muted text is a
|
|
61
|
-
* grey slot, never SGR faint (faint made muted arguments unreadable).
|
|
62
|
-
*/
|
|
63
|
-
const DARK_COLORS: ThemeColors = terminalTokens({ muted: "37", red: "91", green: "92", yellow: "93", blue: "94", magenta: "95", cyan: "96", rule: "90" });
|
|
64
|
-
const LIGHT_COLORS: ThemeColors = terminalTokens({ muted: "90", red: "31", green: "32", yellow: "33", blue: "34", magenta: "35", cyan: "36", rule: "37" });
|
|
65
|
-
|
|
66
|
-
function terminalTokens(slot: { muted: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; rule: string }): ThemeColors {
|
|
67
|
-
return {
|
|
68
|
-
text: "", prompt: "", background: "",
|
|
69
|
-
thinkingText: slot.magenta, toolTitle: "", toolOutput: slot.muted,
|
|
70
|
-
error: slot.red, toolDiffAdded: slot.green, toolDiffRemoved: slot.red,
|
|
71
|
-
status: slot.muted, muted: slot.muted, dim: slot.muted, warning: slot.yellow, success: slot.green, accent: slot.blue,
|
|
72
|
-
mdHeading: slot.blue, mdCode: slot.cyan, mdLink: slot.blue,
|
|
73
|
-
selectedBg: "", userMessageBg: "", toolPendingBg: "", toolSuccessBg: "", toolErrorBg: "",
|
|
74
|
-
border: slot.rule, borderMuted: slot.rule,
|
|
75
|
-
thinkingOff: slot.muted, thinkingMinimal: slot.magenta, thinkingLow: slot.magenta, thinkingMedium: slot.magenta,
|
|
76
|
-
thinkingHigh: slot.magenta, thinkingXhigh: slot.magenta, thinkingMax: slot.magenta,
|
|
77
|
-
syntaxKeyword: slot.magenta, syntaxString: slot.green, syntaxNumber: slot.yellow, syntaxFunction: slot.blue, syntaxComment: slot.muted,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function builtinTheme(name: "dark" | "light"): Theme {
|
|
82
|
-
return name === "light"
|
|
83
|
-
? { name: "light", colors: LIGHT_COLORS }
|
|
84
|
-
: { name: "dark", colors: DARK_COLORS };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* A built-in appearance (dark, light, or system/auto, which reuse them) as
|
|
89
|
-
* opposed to a theme file. Identity, not name: a file may call itself "dark",
|
|
90
|
-
* and parseTheme always copies colours into a new object.
|
|
91
|
-
*/
|
|
92
|
-
export function isBuiltinTheme(theme: Theme): boolean {
|
|
93
|
-
return theme.colors === DARK_COLORS || theme.colors === LIGHT_COLORS;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function parseTheme(json: unknown): ThemeParse {
|
|
97
|
-
const fallback = builtinTheme("dark");
|
|
98
|
-
if (typeof json !== "object" || json === null || Array.isArray(json)) {
|
|
99
|
-
return {
|
|
100
|
-
theme: fallback,
|
|
101
|
-
diagnostics: ["theme is not an object; using dark"],
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
const rec = json as { readonly [k: string]: unknown };
|
|
105
|
-
const diagnostics: string[] = [];
|
|
106
|
-
const name = typeof rec["name"] === "string" && rec["name"].length > 0 ? rec["name"] : "custom";
|
|
107
|
-
const rawColors = rec["colors"];
|
|
108
|
-
const src: { readonly [k: string]: unknown } =
|
|
109
|
-
typeof rawColors === "object" && rawColors !== null && !Array.isArray(rawColors)
|
|
110
|
-
? (rawColors as { readonly [k: string]: unknown })
|
|
111
|
-
: (() => {
|
|
112
|
-
diagnostics.push("theme.colors is missing or not an object; filling from dark");
|
|
113
|
-
return {};
|
|
114
|
-
})();
|
|
115
|
-
|
|
116
|
-
for (const key of Object.keys(src)) {
|
|
117
|
-
if (!isThemeToken(key) && !PI_THEME_TOKENS.includes(key)) diagnostics.push(`unknown token: ${key}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const colors: { [K in ThemeToken]: string } & Record<string, string> = { ...fallback.colors };
|
|
121
|
-
const vars = typeof rec["vars"] === "object" && rec["vars"] !== null ? rec["vars"] as Record<string, unknown> : {};
|
|
122
|
-
const resolve = (value: unknown, seen = new Set<string>()): unknown => {
|
|
123
|
-
if (typeof value !== "string" || !(value in vars)) return value;
|
|
124
|
-
if (seen.has(value)) return null;
|
|
125
|
-
return resolve(vars[value], new Set([...seen, value]));
|
|
126
|
-
};
|
|
127
|
-
for (const token of new Set<string>([...THEME_TOKENS, ...PI_THEME_TOKENS])) {
|
|
128
|
-
if (!(token in src)) continue;
|
|
129
|
-
const parsed = parseColor(resolve(src[token]));
|
|
130
|
-
if (parsed === null) {
|
|
131
|
-
diagnostics.push(`invalid colour for ${token}; using built-in`);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
colors[token] = (token.endsWith("Bg") || token === "background") ? parsed.replace(/^38;/, "48;") : parsed;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!("status" in src) && "dim" in colors) colors.status = colors["dim"]!;
|
|
138
|
-
if (!("prompt" in src)) colors.prompt = colors.text;
|
|
139
|
-
return { theme: { name, colors }, diagnostics };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isThemeToken(key: string): key is ThemeToken {
|
|
143
|
-
return (THEME_TOKENS as readonly string[]).includes(key);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Accept SGR params (`31`, `38;5;180`, `38;2;r;g;b`), `#RRGGBB`, a 256-colour
|
|
148
|
-
* index, or empty (terminal default). Anything else is invalid.
|
|
149
|
-
*/
|
|
150
|
-
export function parseColor(value: unknown): string | null {
|
|
151
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255) {
|
|
152
|
-
return `38;5;${value}`;
|
|
153
|
-
}
|
|
154
|
-
if (typeof value !== "string") return null;
|
|
155
|
-
if (value === "") return "";
|
|
156
|
-
const hex = /^#([0-9a-fA-F]{6})$/.exec(value);
|
|
157
|
-
if (hex) {
|
|
158
|
-
const n = Number.parseInt(hex[1]!, 16);
|
|
159
|
-
const r = (n >> 16) & 255;
|
|
160
|
-
const g = (n >> 8) & 255;
|
|
161
|
-
const b = n & 255;
|
|
162
|
-
return `38;2;${r};${g};${b}`;
|
|
163
|
-
}
|
|
164
|
-
if (/^[0-9]+(?:;[0-9]+)*$/.test(value)) return value;
|
|
165
|
-
return null;
|
|
166
|
-
}
|
package/src/core/thinking.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/** Ported from Pi (MIT), ai/models.ts at 02eed88. Capability policy, independent of provider I/O. */
|
|
2
|
-
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
3
|
-
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
4
|
-
export interface ThinkingModel {
|
|
5
|
-
readonly reasoning: boolean;
|
|
6
|
-
readonly thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>;
|
|
7
|
-
}
|
|
8
|
-
export function asThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
9
|
-
return typeof value === "string" && (THINKING_LEVELS as readonly string[]).includes(value) ? value as ThinkingLevel : undefined;
|
|
10
|
-
}
|
|
11
|
-
export function supportedThinkingLevels(model: ThinkingModel): ThinkingLevel[] {
|
|
12
|
-
if (!model.reasoning) return ["off"];
|
|
13
|
-
return THINKING_LEVELS.filter(level => {
|
|
14
|
-
const mapped = model.thinkingLevelMap?.[level];
|
|
15
|
-
if (mapped === null) return false;
|
|
16
|
-
return level === "xhigh" || level === "max" ? mapped !== undefined : true;
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
export function clampThinkingLevel(model: ThinkingModel, requested: ThinkingLevel): ThinkingLevel {
|
|
20
|
-
const available = supportedThinkingLevels(model);
|
|
21
|
-
const index = THINKING_LEVELS.indexOf(requested);
|
|
22
|
-
for (const level of [...THINKING_LEVELS.slice(index), ...THINKING_LEVELS.slice(0, index).reverse()]) {
|
|
23
|
-
if (available.includes(level)) return level;
|
|
24
|
-
}
|
|
25
|
-
return "off";
|
|
26
|
-
}
|
package/src/core/tokens.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token accounting.
|
|
3
|
-
*
|
|
4
|
-
* The default here is an ESTIMATE and is labelled as one. Real counting needs the
|
|
5
|
-
* provider's own counter (Anthropic's `messages.count_tokens`); a bundled
|
|
6
|
-
* tokenizer would drift from whatever model is actually in use and would be wrong
|
|
7
|
-
* in a way nobody notices. The boundary logic therefore takes a counter as a
|
|
8
|
-
* parameter, so wiring the real one in is a one-line change and nothing in the
|
|
9
|
-
* core pretends to be authoritative.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { Message } from "./project.ts";
|
|
13
|
-
import { estimateImageTokens } from "./images.ts";
|
|
14
|
-
|
|
15
|
-
export interface TokenCounter {
|
|
16
|
-
readonly kind: "estimate" | "exact";
|
|
17
|
-
/**
|
|
18
|
-
* Counts everything the API bills as prompt: tool schemas, the system
|
|
19
|
-
* prompt and the messages.
|
|
20
|
-
*
|
|
21
|
-
* Tools are a parameter because leaving them out made two sides of a
|
|
22
|
-
* boundary decision incomparable -- one came from the provider's billed
|
|
23
|
-
* figure, which includes schemas, the other from a count that silently
|
|
24
|
-
* skipped them, and the difference read as work the node had done.
|
|
25
|
-
*/
|
|
26
|
-
count(messages: readonly Message[], system: string, tools?: readonly unknown[]): number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Estimated tokens for one string.
|
|
31
|
-
*
|
|
32
|
-
* ASCII keeps the original chars/3.6: code and JSON tokenize more densely than
|
|
33
|
-
* prose, so this sits below the usual chars/4 rule, and every measured cut
|
|
34
|
-
* point so far was taken with it. Ideographic scripts are a different
|
|
35
|
-
* regime -- one Han character is roughly 0.6-1.5 tokens across current
|
|
36
|
-
* tokenizers, where chars/3.6 would say 0.28 -- so they are charged a full
|
|
37
|
-
* token each. Overestimating only moves a cut earlier; underestimating is a
|
|
38
|
-
* 400 from the provider. Other non-ASCII (accents, symbols, emoji) sits
|
|
39
|
-
* between the two at half a token per code point.
|
|
40
|
-
*/
|
|
41
|
-
export function estimateText(text: string): number {
|
|
42
|
-
let ascii = 0;
|
|
43
|
-
let weighted = 0;
|
|
44
|
-
for (let i = 0; i < text.length; i++) {
|
|
45
|
-
const code = text.charCodeAt(i);
|
|
46
|
-
if (code < 0x80) { ascii++; continue; }
|
|
47
|
-
// A surrogate pair is one code point; charge it once, at its high half.
|
|
48
|
-
if (code >= 0xdc00 && code <= 0xdfff) continue;
|
|
49
|
-
const ideographic =
|
|
50
|
-
(code >= 0x2e80 && code <= 0x9fff) || // CJK radicals .. unified ideographs, kana, bopomofo
|
|
51
|
-
(code >= 0xac00 && code <= 0xd7af) || // Hangul syllables
|
|
52
|
-
(code >= 0xf900 && code <= 0xfaff) || // CJK compatibility ideographs
|
|
53
|
-
(code >= 0xff00 && code <= 0xffef); // full-width forms
|
|
54
|
-
weighted += ideographic ? 1 : 0.5;
|
|
55
|
-
}
|
|
56
|
-
return ascii / 3.6 + weighted;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* estimateText across message and system text, plus serialized schemas and
|
|
61
|
-
* per-message framing. Expect roughly +/-20% on ASCII; treat the trigger
|
|
62
|
-
* threshold as approximate until an exact counter is wired in.
|
|
63
|
-
*/
|
|
64
|
-
export const estimateTokens: TokenCounter = {
|
|
65
|
-
kind: "estimate",
|
|
66
|
-
count(messages, system, tools = []) {
|
|
67
|
-
// Serialized schemas are what goes on the wire, so their length is the
|
|
68
|
-
// right proxy -- and they are billed on every single request.
|
|
69
|
-
let tokens = estimateText(system) + (tools.length > 0 ? estimateText(JSON.stringify(tools)) : 0);
|
|
70
|
-
for (const m of messages) {
|
|
71
|
-
for (const b of m.content) {
|
|
72
|
-
if (b.type === "text") tokens += estimateText(b.text);
|
|
73
|
-
// Thinking is opaque here; its serialized size is the best proxy.
|
|
74
|
-
else if (b.type === "thinking") tokens += estimateText(JSON.stringify(b.raw));
|
|
75
|
-
else if (b.type === "tool_use") tokens += estimateText(b.name) + estimateText(JSON.stringify(b.input));
|
|
76
|
-
else if (b.type === "tool_result") tokens += estimateText(b.content);
|
|
77
|
-
else if (b.type === "image") tokens += estimateImageTokens(b.image);
|
|
78
|
-
}
|
|
79
|
-
// Per-message framing overhead, roughly.
|
|
80
|
-
tokens += 8 / 3.6;
|
|
81
|
-
}
|
|
82
|
-
return Math.ceil(tokens);
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* A counter whose every count is multiplied by `factor`.
|
|
88
|
-
*
|
|
89
|
-
* Used once the provider has rejected a prompt the estimate said would fit:
|
|
90
|
-
* that is a measurement that this content tokenizes `factor` times denser
|
|
91
|
-
* than estimated, and sizing the retained context with the unscaled counter
|
|
92
|
-
* would reproduce the overflow.
|
|
93
|
-
*/
|
|
94
|
-
export function scaledCounter(counter: TokenCounter, factor: number): TokenCounter {
|
|
95
|
-
return { kind: "estimate", count: (messages, system, tools) => Math.ceil(counter.count(messages, system, tools) * factor) };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* An anchor: the prompt size a provider actually charged, and how far into the
|
|
100
|
-
* conversation that measurement covered.
|
|
101
|
-
*
|
|
102
|
-
* Measuring the estimator against a real tokenizer showed it running between
|
|
103
|
-
* -21% and +38% across realistic content -- prose, source, JSON and YAML all
|
|
104
|
-
* tokenize at different densities, so no single chars-per-token constant fits
|
|
105
|
-
* them at once. Underestimating source by a fifth is the dangerous direction:
|
|
106
|
-
* a 30k trigger would fire at an actual 38k.
|
|
107
|
-
*
|
|
108
|
-
* Calibrating the constant would not fix that, because the error depends on
|
|
109
|
-
* what the content happens to be. Anchoring does: every response reports the
|
|
110
|
-
* prompt it was actually billed for, so everything up to that point is known
|
|
111
|
-
* exactly and only the delta since needs estimating. The error then scales
|
|
112
|
-
* with one turn instead of with the whole conversation.
|
|
113
|
-
*/
|
|
114
|
-
export interface Anchor {
|
|
115
|
-
/** Sum of uncached, cache-write and cache-read input tokens. */
|
|
116
|
-
promptTokens: number;
|
|
117
|
-
/** Tokens the model generated, which become part of the next prompt. */
|
|
118
|
-
outputTokens: number;
|
|
119
|
-
/** Number of messages the measurement covered. */
|
|
120
|
-
messageCount: number;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Prompt size for the next request: exact up to the anchor, estimated after.
|
|
125
|
-
*
|
|
126
|
-
* Falls back to a plain estimate before the first response, which is the only
|
|
127
|
-
* point where nothing has been measured yet.
|
|
128
|
-
*/
|
|
129
|
-
export function anchoredCount(
|
|
130
|
-
messages: readonly Message[],
|
|
131
|
-
system: string,
|
|
132
|
-
anchor: Anchor | null,
|
|
133
|
-
counter: TokenCounter = estimateTokens,
|
|
134
|
-
tools: readonly unknown[] = [],
|
|
135
|
-
): { tokens: number; exactUpTo: number } {
|
|
136
|
-
if (anchor === null || anchor.messageCount > messages.length) {
|
|
137
|
-
return { tokens: counter.count(messages, system, tools), exactUpTo: 0 };
|
|
138
|
-
}
|
|
139
|
-
// The anchored prompt already includes the system prompt and the tools, so
|
|
140
|
-
// the delta is counted without either to avoid charging for them twice.
|
|
141
|
-
// The measured output is already the first assistant message after the
|
|
142
|
-
// measured prompt. Its billed count replaces, rather than adds to, an
|
|
143
|
-
// estimate of that same message (D1).
|
|
144
|
-
const outputIndex = messages[anchor.messageCount]?.role === "assistant" ? 1 : 0;
|
|
145
|
-
const since = messages.slice(anchor.messageCount + outputIndex);
|
|
146
|
-
const delta = counter.count(since, "", []);
|
|
147
|
-
return {
|
|
148
|
-
tokens: anchor.promptTokens + anchor.outputTokens + delta,
|
|
149
|
-
exactUpTo: anchor.promptTokens,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/** O.3: one declarative input contract serves model schemas and runtime admission. */
|
|
2
|
-
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };
|
|
3
|
-
export type ToolDiagnostic = {
|
|
4
|
-
code: "invalid_input" | "version_mismatch" | "not_found" | "ambiguous_edit" | "permission_denied" | "execution_failed" |
|
|
5
|
-
"environment_missing" | "transient" | "cancelled" | "timeout" | "unknown_effect" | "command_failed";
|
|
6
|
-
message: string;
|
|
7
|
-
recover?: { tool: string; input: { readonly [key: string]: JsonValue } };
|
|
8
|
-
/** R3: optional only for legacy records; supplied together by current failure owners. */
|
|
9
|
-
execution?: "not_started" | "started" | "unknown";
|
|
10
|
-
effects?: "none" | "possible";
|
|
11
|
-
retry?: "never" | "after_change" | "after_inspection" | "backoff";
|
|
12
|
-
next?: string;
|
|
13
|
-
/** A schema-valid shape with placeholders, never an automatically executable recovery. */
|
|
14
|
-
example?: { readonly [key: string]: JsonValue };
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function isJsonValue(value: unknown): value is JsonValue {
|
|
18
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
19
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
20
|
-
return typeof value === "object" && (Array.isArray(value) ? value : Object.values(value)).every(isJsonValue);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function isToolDiagnostic(value: unknown): value is ToolDiagnostic {
|
|
24
|
-
if (value === null || typeof value !== "object") return false;
|
|
25
|
-
const item = value as Record<string, unknown>;
|
|
26
|
-
if (!["invalid_input", "version_mismatch", "not_found", "ambiguous_edit", "permission_denied", "execution_failed",
|
|
27
|
-
"environment_missing", "transient", "cancelled", "timeout", "unknown_effect", "command_failed"].includes(String(item["code"])) || typeof item["message"] !== "string") return false;
|
|
28
|
-
if (["execution", "effects", "retry", "next"].some(key => item[key] !== undefined)) {
|
|
29
|
-
if (!["not_started", "started", "unknown"].includes(String(item["execution"])) ||
|
|
30
|
-
!["none", "possible"].includes(String(item["effects"])) ||
|
|
31
|
-
!["never", "after_change", "after_inspection", "backoff"].includes(String(item["retry"])) || typeof item["next"] !== "string") return false;
|
|
32
|
-
if (item["retry"] === "backoff" && (item["code"] !== "transient" || item["effects"] !== "none")) return false;
|
|
33
|
-
if (item["code"] === "permission_denied" && (item["retry"] !== "never" || item["recover"] !== undefined)) return false;
|
|
34
|
-
if (item["code"] === "unknown_effect" && item["retry"] !== "after_inspection") return false;
|
|
35
|
-
}
|
|
36
|
-
if (item["example"] !== undefined && (item["example"] === null || typeof item["example"] !== "object" || Array.isArray(item["example"]) || !isJsonValue(item["example"]))) return false;
|
|
37
|
-
const recover = item["recover"];
|
|
38
|
-
if (recover === undefined) return true;
|
|
39
|
-
if (recover === null || typeof recover !== "object") return false;
|
|
40
|
-
const recovery = recover as Record<string, unknown>;
|
|
41
|
-
return typeof recovery["tool"] === "string" && recovery["input"] !== null &&
|
|
42
|
-
typeof recovery["input"] === "object" && !Array.isArray(recovery["input"]) && isJsonValue(recovery["input"]);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Required fields only; examples express a valid shape, not permission or a safe command. */
|
|
46
|
-
export function minimalInput(schema: unknown): JsonValue | undefined {
|
|
47
|
-
if (schema === null || typeof schema !== "object") return undefined;
|
|
48
|
-
const s = schema as Record<string, unknown>;
|
|
49
|
-
if (Array.isArray(s["enum"]) && s["enum"].length && isJsonValue(s["enum"][0])) return s["enum"][0];
|
|
50
|
-
switch (s["type"]) {
|
|
51
|
-
case "string": return "<value>".padEnd(typeof s["minLength"] === "number" ? Math.min(4096, s["minLength"]) : 0, "x");
|
|
52
|
-
case "number": case "integer": return typeof s["minimum"] === "number" ? s["minimum"] : 0;
|
|
53
|
-
case "boolean": return false;
|
|
54
|
-
case "array": return [];
|
|
55
|
-
case "object": {
|
|
56
|
-
const value: { [key: string]: JsonValue } = {};
|
|
57
|
-
const properties = s["properties"];
|
|
58
|
-
for (const key of Array.isArray(s["required"]) ? s["required"] : []) {
|
|
59
|
-
if (typeof key !== "string" || properties === null || typeof properties !== "object") return undefined;
|
|
60
|
-
const item = minimalInput((properties as Record<string, unknown>)[key]);
|
|
61
|
-
if (item === undefined) return undefined;
|
|
62
|
-
value[key] = item;
|
|
63
|
-
}
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
default: return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Diagnostics are projected as tool data; they never become trusted system instructions. */
|
|
71
|
-
export function diagnosticContent(content: string, diagnostic?: ToolDiagnostic): string {
|
|
72
|
-
if (diagnostic?.execution === undefined) return content;
|
|
73
|
-
return content + "\nTool diagnostic: " + JSON.stringify(diagnostic);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
import { editDistance } from "./text-normalize.ts";
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Names models send for arguments that exist under another name here. Edit
|
|
80
|
-
* distance cannot see these (cwd and workdir share no letters in order);
|
|
81
|
-
* each entry is a spelling another harness uses for the same argument.
|
|
82
|
-
*/
|
|
83
|
-
const SYNONYMS: Readonly<Record<string, readonly string[]>> = {
|
|
84
|
-
cwd: ["workdir"], dir: ["workdir", "path"], directory: ["workdir", "path"],
|
|
85
|
-
file: ["path"], file_path: ["path"], filepath: ["path"], filename: ["path"],
|
|
86
|
-
cmd: ["command"], script: ["command"],
|
|
87
|
-
text: ["content"], contents: ["content"], old_str: ["old_string"], new_str: ["new_string"],
|
|
88
|
-
query: ["pattern"], regex: ["pattern"], timeout_ms: ["timeout"], offset: ["start"], limit: ["end", "limit"],
|
|
89
|
-
// Different units from max_output_tokens, so a hint rather than a silent conversion.
|
|
90
|
-
max_output_chars: ["max_output_tokens"], max_output_bytes: ["max_output_tokens"], max_tokens: ["max_output_tokens"],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/** The candidate nearest to `name`, when it is near enough to be the likely intent. */
|
|
94
|
-
export function closestName(name: string, candidates: readonly string[]): string | undefined {
|
|
95
|
-
const synonym = SYNONYMS[name.toLowerCase()]?.find((alias) => candidates.includes(alias));
|
|
96
|
-
if (synonym !== undefined) return synonym;
|
|
97
|
-
let best: string | undefined, distance = Infinity;
|
|
98
|
-
for (const candidate of candidates) {
|
|
99
|
-
const d = Math.min(editDistance(name.toLowerCase(), candidate.toLowerCase()),
|
|
100
|
-
// A common drift is a synonym sharing a stem (replaceAll / replace_all).
|
|
101
|
-
editDistance(name.toLowerCase().replace(/[_-]/g, ""), candidate.toLowerCase().replace(/[_-]/g, "")));
|
|
102
|
-
if (d < distance) { best = candidate; distance = d; }
|
|
103
|
-
}
|
|
104
|
-
return best !== undefined && distance <= Math.max(2, Math.floor(best.length / 2)) ? best : undefined;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Validate the schema vocabulary used by built-ins; semantic constraints stay with their owner.
|
|
109
|
-
*
|
|
110
|
-
* An argument the schema does not declare is an error, not noise: a model
|
|
111
|
-
* that sends `cwd` believes it changed directory, and dropping the field
|
|
112
|
-
* silently runs the command somewhere it did not intend. `hidden` names
|
|
113
|
-
* internal arguments accepted at the top level without being advertised.
|
|
114
|
-
*/
|
|
115
|
-
export function inputError(schema: unknown, value: unknown, path = "input", hidden: readonly string[] = []): string | null {
|
|
116
|
-
if (schema === null || typeof schema !== "object") return null;
|
|
117
|
-
const s = schema as Record<string, unknown>;
|
|
118
|
-
const type = s["type"];
|
|
119
|
-
if (type === "object" && (value === null || typeof value !== "object" || Array.isArray(value))) return `${path} must be an object`;
|
|
120
|
-
if (type === "array" && !Array.isArray(value)) return `${path} must be an array`;
|
|
121
|
-
if (["string", "boolean", "number"].includes(String(type)) && typeof value !== type) return `${path} must be ${String(type)}`;
|
|
122
|
-
if (type === "integer" && (typeof value !== "number" || !Number.isSafeInteger(value))) return `${path} must be a positive integer or zero where allowed`;
|
|
123
|
-
if (typeof value === "number" && !Number.isFinite(value)) return `${path} must be finite`;
|
|
124
|
-
if (typeof value === "number" && typeof s["minimum"] === "number" && value < s["minimum"]) return `${path} must be >= ${s["minimum"]}`;
|
|
125
|
-
if (typeof value === "number" && typeof s["maximum"] === "number" && value > s["maximum"]) return `${path} must be <= ${s["maximum"]}`;
|
|
126
|
-
if (typeof value === "string" && typeof s["minLength"] === "number" && value.length < s["minLength"]) return `${path} must not be empty`;
|
|
127
|
-
if (Array.isArray(s["enum"]) && !s["enum"].includes(value)) return `${path} must be one of ${s["enum"].join(", ")}`;
|
|
128
|
-
if (type === "array" && Array.isArray(value)) {
|
|
129
|
-
for (let i = 0; i < value.length; i++) { const error = inputError(s["items"], value[i], `${path}[${i}]`); if (error) return error; }
|
|
130
|
-
}
|
|
131
|
-
if (type === "object" && value !== null && typeof value === "object") {
|
|
132
|
-
const object = value as Record<string, unknown>;
|
|
133
|
-
for (const key of Array.isArray(s["required"]) ? s["required"] : []) {
|
|
134
|
-
if (typeof key === "string" && object[key] === undefined) return `${path}.${key} is required`;
|
|
135
|
-
}
|
|
136
|
-
const properties = s["properties"];
|
|
137
|
-
if (properties !== null && typeof properties === "object") {
|
|
138
|
-
const declared = Object.keys(properties);
|
|
139
|
-
for (const key of Object.keys(object)) {
|
|
140
|
-
if (declared.includes(key) || hidden.includes(key) || object[key] === undefined) continue;
|
|
141
|
-
const near = closestName(key, declared);
|
|
142
|
-
return `unknown argument ${path === "input" ? key : `${path}.${key}`}` + (near === undefined ? "" : `; did you mean ${near}?`) +
|
|
143
|
-
` Valid arguments: ${[...declared].sort().join(", ")}`;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (properties !== null && typeof properties === "object") for (const [key, property] of Object.entries(properties)) {
|
|
147
|
-
if (object[key] === undefined) continue;
|
|
148
|
-
const error = inputError(property, object[key], `${path}.${key}`);
|
|
149
|
-
if (error) return error;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/** R11 / D6 / D8: local discovery proposes declarations; only a paired checkpoint installs them. */
|
|
2
|
-
import type { LogEvent, ToolInvocationEvent } from "./events.ts";
|
|
3
|
-
|
|
4
|
-
/** Assumed response bounds, not measured model optima; prevent a directory dump from replacing a lookup. */
|
|
5
|
-
export const MAX_DISCOVERY_RESULTS = 8;
|
|
6
|
-
export const MAX_DISCOVERY_QUERY = 256;
|
|
7
|
-
const MAX_PARAMETER_HINTS = 8;
|
|
8
|
-
const MAX_PURPOSE_CHARS = 320;
|
|
9
|
-
|
|
10
|
-
export interface DiscoverableTool {
|
|
11
|
-
readonly name: string;
|
|
12
|
-
readonly description: string;
|
|
13
|
-
readonly input_schema: { readonly properties: Readonly<Record<string, unknown>>; readonly required: readonly string[] };
|
|
14
|
-
}
|
|
15
|
-
export interface DeclarationRequest { readonly name: string; readonly fingerprint: string }
|
|
16
|
-
export interface DiscoveryMatch {
|
|
17
|
-
name: string; purpose: string; inputs: { name: string; type: string; required: boolean }[]; omitted_inputs: number;
|
|
18
|
-
}
|
|
19
|
-
export function declaredToolNames(tools: readonly unknown[]): string[] {
|
|
20
|
-
return tools.flatMap(tool => tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string" ? [tool.name] : []);
|
|
21
|
-
}
|
|
22
|
-
const compare = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
|
|
23
|
-
function record(raw: unknown): Record<string, unknown> | undefined {
|
|
24
|
-
return raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : undefined;
|
|
25
|
-
}
|
|
26
|
-
function parameterType(raw: unknown): string {
|
|
27
|
-
if (raw === null || typeof raw !== "object") return "schema";
|
|
28
|
-
const type = (raw as Record<string, unknown>)["type"];
|
|
29
|
-
if (typeof type === "string") return type.slice(0, 64);
|
|
30
|
-
if (Array.isArray(type) && type.every(item => typeof item === "string")) return type.join("|").slice(0, 64);
|
|
31
|
-
return "schema";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Pure, deterministic matching over the caller's already-selected directory; never perform discovery I/O. */
|
|
35
|
-
export function discoverTools(catalog: readonly DiscoverableTool[], query: string, limit = 5): DiscoveryMatch[] {
|
|
36
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_DISCOVERY_RESULTS) throw new Error("invalid discovery limit");
|
|
37
|
-
const normalized = query.trim().toLowerCase();
|
|
38
|
-
if (!normalized || query.length > MAX_DISCOVERY_QUERY) throw new Error("query must contain 1 to 256 characters");
|
|
39
|
-
const terms = normalized.split(/[^\p{L}\p{N}_-]+/u).filter(Boolean);
|
|
40
|
-
if (terms.length === 0) return [];
|
|
41
|
-
return catalog.map(tool => {
|
|
42
|
-
const name = tool.name.toLowerCase(), text = `${name} ${tool.description.toLowerCase()}`;
|
|
43
|
-
const score = name === normalized ? 100 : terms.every(term => text.includes(term))
|
|
44
|
-
? 1 + terms.filter(term => name.includes(term)).length : 0;
|
|
45
|
-
return { tool, score };
|
|
46
|
-
}).filter(item => item.score > 0).sort((a, b) => b.score - a.score || compare(a.tool.name, b.tool.name))
|
|
47
|
-
.slice(0, limit).map(({ tool }) => {
|
|
48
|
-
const required = new Set(tool.input_schema.required);
|
|
49
|
-
const parameters = Object.entries(tool.input_schema.properties).sort(([a], [b]) =>
|
|
50
|
-
Number(required.has(b)) - Number(required.has(a)) || compare(a, b));
|
|
51
|
-
return { name: tool.name, purpose: tool.description.slice(0, MAX_PURPOSE_CHARS),
|
|
52
|
-
inputs: parameters.slice(0, MAX_PARAMETER_HINTS).map(([name, raw]) => ({ name: name.slice(0, 128), type: parameterType(raw), required: required.has(name) })),
|
|
53
|
-
omitted_inputs: Math.max(0, parameters.length - MAX_PARAMETER_HINTS) };
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** A model assertion or another extension's data cannot authorize a declaration (D11). */
|
|
58
|
-
export function recordedDeclarations(events: readonly LogEvent[]): DeclarationRequest[] {
|
|
59
|
-
const invocations = new Map<string, ToolInvocationEvent>();
|
|
60
|
-
const requested = new Map<string, { request: DeclarationRequest; user: string | undefined }>();
|
|
61
|
-
let user: string | undefined;
|
|
62
|
-
for (const event of events) {
|
|
63
|
-
if (event.t === "user") { user = event.id; continue; }
|
|
64
|
-
if (event.t === "interrupt") {
|
|
65
|
-
for (const [name, value] of requested) if (value.user === event.user_id) requested.delete(name);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (event.t === "tool_invocation") { invocations.set(event.id, event); continue; }
|
|
69
|
-
if (event.t !== "tool_result" || !event.ok || !event.invocation_ref) continue;
|
|
70
|
-
if (event.outcome !== undefined && event.outcome.status !== "completed") continue;
|
|
71
|
-
const invocation = invocations.get(event.invocation_ref.event);
|
|
72
|
-
if (invocation?.name !== "tool_search" || invocation.call_id !== event.call_id) continue;
|
|
73
|
-
const data = record(event.data);
|
|
74
|
-
if (!data || data["kind"] !== "tool_discovery" || data["version"] !== 1) continue;
|
|
75
|
-
const entries = data["requested"];
|
|
76
|
-
if (!Array.isArray(entries) || entries.length > MAX_DISCOVERY_RESULTS) continue;
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
const item = record(entry);
|
|
79
|
-
if (!item) continue;
|
|
80
|
-
const name = item["name"], fingerprint = item["fingerprint"];
|
|
81
|
-
if (typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(name) && typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint)) {
|
|
82
|
-
requested.set(name, { request: { name, fingerprint }, user });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return [...requested.values()].map(value => value.request);
|
|
87
|
-
}
|