@visual-z/xeno 0.1.3 → 0.2.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 +19 -0
- package/README.md +143 -33
- package/docs/install.md +18 -6
- package/docs/providers.md +147 -0
- package/package.json +8 -2
- package/src/apps/cli.ts +42 -14
- package/src/apps/commands.ts +32 -4
- package/src/apps/completions.ts +9 -6
- package/src/apps/export-cli.ts +24 -0
- package/src/apps/extension-ui.ts +330 -0
- package/src/apps/input.ts +16 -1
- package/src/apps/model-catalog.ts +42 -0
- package/src/apps/project-trust.ts +49 -0
- package/src/apps/render.ts +1 -0
- package/src/apps/resource-browser.ts +75 -0
- package/src/apps/resources.ts +25 -0
- package/src/apps/session-browser.ts +113 -0
- package/src/apps/session-picker.ts +55 -0
- package/src/apps/shell.ts +1177 -378
- package/src/apps/startup.ts +400 -268
- package/src/core/action-defaults.ts +306 -0
- package/src/core/attachment-input.ts +17 -0
- package/src/core/catalog-merge.ts +19 -0
- package/src/core/event-validation.ts +19 -2
- package/src/core/events.ts +45 -2
- package/src/core/export.ts +6 -2
- package/src/core/extension-flags.ts +14 -0
- package/src/core/images.ts +46 -0
- package/src/core/json-event.ts +2 -0
- package/src/core/keymap.ts +43 -119
- package/src/core/model-replay.ts +22 -0
- package/src/core/model-scope.ts +53 -0
- package/src/core/packages.ts +47 -0
- package/src/core/progress.ts +9 -0
- package/src/core/project.ts +21 -3
- package/src/core/prompts.ts +33 -5
- package/src/core/queue.ts +18 -0
- package/src/core/resource-selection.ts +36 -0
- package/src/{runtime → core}/resume.ts +15 -22
- package/src/core/session-archive.ts +4 -0
- package/src/core/session-browser.ts +82 -0
- package/src/core/session-entry.ts +56 -0
- package/src/core/session-position.ts +26 -0
- package/src/core/settings.ts +169 -154
- package/src/core/theme.ts +15 -6
- package/src/core/thinking.ts +26 -0
- package/src/core/tokens.ts +2 -0
- package/src/core/transcript.ts +15 -5
- package/src/runtime/auth.ts +116 -11
- package/src/runtime/catalog.ts +54 -151
- package/src/runtime/clipboard.ts +114 -0
- package/src/runtime/extension-loader.ts +37 -0
- package/src/runtime/extension-ui.ts +55 -0
- package/src/runtime/extensions.ts +273 -0
- package/src/runtime/images.ts +76 -0
- package/src/runtime/loop.ts +213 -83
- package/src/runtime/model-config.ts +232 -0
- package/src/runtime/model-data.LICENSE +27 -0
- package/src/runtime/model-data.ts +21 -0
- package/src/runtime/model-replay.ts +10 -0
- package/src/runtime/model-scope.ts +20 -0
- package/src/runtime/model-sources.ts +151 -0
- package/src/runtime/model.ts +74 -11
- package/src/runtime/models-fetch.ts +6 -104
- package/src/runtime/oauth.ts +4 -3
- package/src/runtime/openai-responses.ts +12 -4
- package/src/runtime/openai.ts +13 -4
- package/src/runtime/package-manager.ts +125 -0
- package/src/runtime/pi-provider.ts +235 -0
- package/src/runtime/progress.ts +26 -0
- package/src/runtime/provider-access.ts +21 -0
- package/src/runtime/provider-protocols.ts +29 -0
- package/src/runtime/provider-request.ts +73 -0
- package/src/runtime/provider.ts +26 -6
- package/src/runtime/rich-text.ts +36 -0
- package/src/runtime/tools.ts +41 -5
- package/src/store/command.ts +7 -1
- package/src/store/context-files.ts +1 -1
- package/src/store/log.ts +65 -0
- package/src/store/models.ts +28 -9
- package/src/store/observations.ts +4 -2
- package/src/store/packages.ts +98 -0
- package/src/store/recovery.ts +8 -0
- package/src/store/resource-index.ts +170 -0
- package/src/store/resources.ts +75 -142
- package/src/store/session-archive.ts +162 -0
- package/src/store/session-browser.ts +68 -0
- package/src/store/session-position.ts +27 -0
- package/src/store/sessions.ts +87 -66
- package/src/store/trust.ts +13 -0
- package/src/store/workspace.ts +6 -0
- package/src/tui/chat-viewport.ts +47 -0
- package/src/tui/editor.ts +62 -15
- package/src/tui/host.ts +50 -9
- package/src/tui/input.ts +8 -2
- package/src/tui/select.ts +24 -6
- package/src/tui/settings.ts +4 -0
- package/src/tui/system-theme.ts +700 -0
- package/src/tui/transcript.ts +63 -11
- package/src/tui/tree.ts +161 -95
- package/src/vendor/pi-rich/LICENSE.upstream +21 -0
- package/src/vendor/pi-rich/UPSTREAM.json +17 -0
- package/src/vendor/pi-rich/clipboard-command.ts +44 -0
- package/src/vendor/pi-rich/exif-orientation.ts +182 -0
- package/src/vendor/pi-rich/html.ts +51 -0
- package/src/vendor/pi-rich/image-convert.ts +49 -0
- package/src/vendor/pi-rich/image-resize-core.ts +164 -0
- package/src/vendor/pi-rich/image-resize-worker.ts +42 -0
- package/src/vendor/pi-rich/image-resize.ts +123 -0
- package/src/vendor/pi-rich/mime.ts +116 -0
- package/src/vendor/pi-rich/photon.ts +139 -0
- package/src/vendor/pi-rich/syntax-highlight.ts +214 -0
- package/src/vendor/pi-tui/UPSTREAM.json +50 -0
- package/src/vendor/pi-tui/VENDOR.md +7 -5
- package/src/vendor/pi-tui/autocomplete.ts +35 -10
- package/src/vendor/pi-tui/colors.ts +367 -0
- package/src/vendor/pi-tui/components/box.ts +8 -9
- package/src/vendor/pi-tui/components/editor.ts +4 -2
- package/src/vendor/pi-tui/components/markdown.ts +8 -2
- package/src/vendor/pi-tui/index.ts +31 -1
- package/src/vendor/pi-tui/native/UPSTREAM.json +23 -0
- package/src/vendor/pi-tui/native/clipboard.h +208 -0
- package/src/vendor/pi-tui/native/darwin/README.md +22 -0
- package/src/vendor/pi-tui/native/darwin/build.sh +69 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +132 -0
- package/src/vendor/pi-tui/native/linux/README.md +23 -0
- package/src/vendor/pi-tui/native/linux/build.sh +65 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +70 -0
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +274 -0
- package/src/vendor/pi-tui/native/napi.h +91 -0
- package/src/vendor/pi-tui/native/win32/README.md +31 -0
- package/src/vendor/pi-tui/native/win32/build.mjs +232 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +241 -0
- package/src/vendor/pi-tui/native-platform.ts +2 -0
- package/src/vendor/pi-tui/oklab.ts +233 -0
- package/src/vendor/pi-tui/terminal-colors.ts +26 -8
- package/src/vendor/pi-tui/terminal-image.ts +43 -8
- package/src/vendor/pi-tui/terminal.ts +16 -9
- package/src/vendor/pi-tui/tui-alt-screen.ts +26 -18
- package/src/vendor/pi-tui/tui.ts +112 -75
- package/src/vendor/pi-tui/utils.ts +89 -49
- package/src/vendor/pi-tui/wheel-scroll.ts +82 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/** Adapted from Pi keybindings at 02eed88 (MIT). Platform-neutral defaults; overrides share scoped ownership. */
|
|
2
|
+
export const ACTION_DEFAULTS = {
|
|
3
|
+
"editor.cursorUp": [
|
|
4
|
+
"up"
|
|
5
|
+
],
|
|
6
|
+
"editor.cursorDown": [
|
|
7
|
+
"down"
|
|
8
|
+
],
|
|
9
|
+
"tui.editor.historyPrevious": [],
|
|
10
|
+
"tui.editor.historyNext": [],
|
|
11
|
+
"editor.cursorLeft": [
|
|
12
|
+
"left",
|
|
13
|
+
"ctrl+b"
|
|
14
|
+
],
|
|
15
|
+
"editor.cursorRight": [
|
|
16
|
+
"right",
|
|
17
|
+
"ctrl+f"
|
|
18
|
+
],
|
|
19
|
+
"editor.cursorWordLeft": [
|
|
20
|
+
"alt+left",
|
|
21
|
+
"ctrl+left",
|
|
22
|
+
"alt+b"
|
|
23
|
+
],
|
|
24
|
+
"editor.cursorWordRight": [
|
|
25
|
+
"alt+right",
|
|
26
|
+
"ctrl+right",
|
|
27
|
+
"alt+f"
|
|
28
|
+
],
|
|
29
|
+
"editor.cursorLineStart": [
|
|
30
|
+
"home",
|
|
31
|
+
"ctrl+home",
|
|
32
|
+
"ctrl+a"
|
|
33
|
+
],
|
|
34
|
+
"editor.cursorLineEnd": [
|
|
35
|
+
"end",
|
|
36
|
+
"ctrl+end",
|
|
37
|
+
"ctrl+e"
|
|
38
|
+
],
|
|
39
|
+
"tui.editor.jumpForward": [
|
|
40
|
+
"ctrl+]"
|
|
41
|
+
],
|
|
42
|
+
"tui.editor.jumpBackward": [
|
|
43
|
+
"ctrl+alt+]"
|
|
44
|
+
],
|
|
45
|
+
"tui.editor.pageUp": [
|
|
46
|
+
"pageUp",
|
|
47
|
+
"ctrl+pageUp"
|
|
48
|
+
],
|
|
49
|
+
"tui.editor.pageDown": [
|
|
50
|
+
"pageDown",
|
|
51
|
+
"ctrl+pageDown"
|
|
52
|
+
],
|
|
53
|
+
"editor.deleteCharBackward": [
|
|
54
|
+
"backspace"
|
|
55
|
+
],
|
|
56
|
+
"editor.deleteCharForward": [
|
|
57
|
+
"delete",
|
|
58
|
+
"ctrl+d"
|
|
59
|
+
],
|
|
60
|
+
"editor.deleteWordBackward": [
|
|
61
|
+
"ctrl+w",
|
|
62
|
+
"alt+backspace"
|
|
63
|
+
],
|
|
64
|
+
"editor.deleteWordForward": [
|
|
65
|
+
"alt+d",
|
|
66
|
+
"alt+delete"
|
|
67
|
+
],
|
|
68
|
+
"editor.deleteToLineStart": [
|
|
69
|
+
"ctrl+u"
|
|
70
|
+
],
|
|
71
|
+
"editor.deleteToLineEnd": [
|
|
72
|
+
"ctrl+k"
|
|
73
|
+
],
|
|
74
|
+
"editor.yank": [
|
|
75
|
+
"ctrl+y"
|
|
76
|
+
],
|
|
77
|
+
"tui.editor.yankPop": [
|
|
78
|
+
"alt+y"
|
|
79
|
+
],
|
|
80
|
+
"editor.undo": [
|
|
81
|
+
"ctrl+-",
|
|
82
|
+
"ctrl+_"
|
|
83
|
+
],
|
|
84
|
+
"editor.newLine": [
|
|
85
|
+
"shift+enter",
|
|
86
|
+
"ctrl+j"
|
|
87
|
+
],
|
|
88
|
+
"editor.submit": [
|
|
89
|
+
"enter"
|
|
90
|
+
],
|
|
91
|
+
"editor.complete": [
|
|
92
|
+
"tab"
|
|
93
|
+
],
|
|
94
|
+
"tui.input.copy": [
|
|
95
|
+
"ctrl+c"
|
|
96
|
+
],
|
|
97
|
+
"tui.select.up": [
|
|
98
|
+
"up"
|
|
99
|
+
],
|
|
100
|
+
"tui.select.down": [
|
|
101
|
+
"down"
|
|
102
|
+
],
|
|
103
|
+
"tui.select.pageUp": [
|
|
104
|
+
"pageUp"
|
|
105
|
+
],
|
|
106
|
+
"tui.select.pageDown": [
|
|
107
|
+
"pageDown"
|
|
108
|
+
],
|
|
109
|
+
"tui.select.confirm": [
|
|
110
|
+
"enter"
|
|
111
|
+
],
|
|
112
|
+
"tui.select.cancel": [
|
|
113
|
+
"escape",
|
|
114
|
+
"ctrl+c"
|
|
115
|
+
],
|
|
116
|
+
"tui.altScreen.pageUp": [
|
|
117
|
+
"pageUp"
|
|
118
|
+
],
|
|
119
|
+
"tui.altScreen.pageDown": [
|
|
120
|
+
"pageDown"
|
|
121
|
+
],
|
|
122
|
+
"tui.altScreen.halfPageUp": [],
|
|
123
|
+
"tui.altScreen.halfPageDown": [],
|
|
124
|
+
"tui.altScreen.lineUp": [],
|
|
125
|
+
"tui.altScreen.lineDown": [],
|
|
126
|
+
"tui.altScreen.previousPrompt": [
|
|
127
|
+
"ctrl+shift+up",
|
|
128
|
+
"ctrl+up"
|
|
129
|
+
],
|
|
130
|
+
"tui.altScreen.nextPrompt": [
|
|
131
|
+
"ctrl+shift+down",
|
|
132
|
+
"ctrl+down"
|
|
133
|
+
],
|
|
134
|
+
"tui.altScreen.search": [
|
|
135
|
+
"ctrl+shift+f"
|
|
136
|
+
],
|
|
137
|
+
"tui.altScreen.searchNext": [
|
|
138
|
+
"enter",
|
|
139
|
+
"ctrl+g"
|
|
140
|
+
],
|
|
141
|
+
"tui.altScreen.searchPrevious": [
|
|
142
|
+
"shift+enter",
|
|
143
|
+
"ctrl+shift+g"
|
|
144
|
+
],
|
|
145
|
+
"tui.altScreen.searchClose": [
|
|
146
|
+
"escape"
|
|
147
|
+
],
|
|
148
|
+
"tui.altScreen.top": [
|
|
149
|
+
"home"
|
|
150
|
+
],
|
|
151
|
+
"tui.altScreen.bottom": [
|
|
152
|
+
"end"
|
|
153
|
+
],
|
|
154
|
+
"app.interrupt": [
|
|
155
|
+
"escape"
|
|
156
|
+
],
|
|
157
|
+
"app.clear": [
|
|
158
|
+
"ctrl+c"
|
|
159
|
+
],
|
|
160
|
+
"app.exit": [
|
|
161
|
+
"ctrl+d"
|
|
162
|
+
],
|
|
163
|
+
"app.suspend": [
|
|
164
|
+
"ctrl+z"
|
|
165
|
+
],
|
|
166
|
+
"app.cycleThinking": [
|
|
167
|
+
"shift+tab"
|
|
168
|
+
],
|
|
169
|
+
"app.saveDefault": [
|
|
170
|
+
"ctrl+s"
|
|
171
|
+
],
|
|
172
|
+
"app.cycleModel": [
|
|
173
|
+
"ctrl+p"
|
|
174
|
+
],
|
|
175
|
+
"app.cycleModelReverse": [
|
|
176
|
+
"shift+ctrl+p"
|
|
177
|
+
],
|
|
178
|
+
"app.model": [
|
|
179
|
+
"ctrl+l"
|
|
180
|
+
],
|
|
181
|
+
"app.toggleTools": [
|
|
182
|
+
"ctrl+o"
|
|
183
|
+
],
|
|
184
|
+
"app.toggleThinking": [
|
|
185
|
+
"ctrl+t"
|
|
186
|
+
],
|
|
187
|
+
"app.session.toggleNamedFilter": [
|
|
188
|
+
"ctrl+n"
|
|
189
|
+
],
|
|
190
|
+
"app.externalEditor": [
|
|
191
|
+
"ctrl+g"
|
|
192
|
+
],
|
|
193
|
+
"app.message.copy": [
|
|
194
|
+
"ctrl+x"
|
|
195
|
+
],
|
|
196
|
+
"app.message.followUp": [
|
|
197
|
+
"alt+enter"
|
|
198
|
+
],
|
|
199
|
+
"app.message.dequeue": [
|
|
200
|
+
"alt+up"
|
|
201
|
+
],
|
|
202
|
+
"app.clipboard.pasteImage": [
|
|
203
|
+
"ctrl+v"
|
|
204
|
+
],
|
|
205
|
+
"app.session.new": [],
|
|
206
|
+
"app.session.tree": [],
|
|
207
|
+
"app.session.fork": [],
|
|
208
|
+
"app.session.resume": [],
|
|
209
|
+
"app.tree.foldOrUp": [
|
|
210
|
+
"alt+left",
|
|
211
|
+
"ctrl+left"
|
|
212
|
+
],
|
|
213
|
+
"app.tree.unfoldOrDown": [
|
|
214
|
+
"alt+right",
|
|
215
|
+
"ctrl+right"
|
|
216
|
+
],
|
|
217
|
+
"app.tree.editLabel": [
|
|
218
|
+
"shift+l"
|
|
219
|
+
],
|
|
220
|
+
"app.tree.toggleLabelTimestamp": [
|
|
221
|
+
"shift+t"
|
|
222
|
+
],
|
|
223
|
+
"app.session.togglePath": [
|
|
224
|
+
"ctrl+p"
|
|
225
|
+
],
|
|
226
|
+
"app.session.toggleSort": [
|
|
227
|
+
"ctrl+s"
|
|
228
|
+
],
|
|
229
|
+
"app.session.rename": [
|
|
230
|
+
"ctrl+r"
|
|
231
|
+
],
|
|
232
|
+
"app.session.delete": [
|
|
233
|
+
"ctrl+d"
|
|
234
|
+
],
|
|
235
|
+
"app.session.deleteNoninvasive": [
|
|
236
|
+
"ctrl+backspace"
|
|
237
|
+
],
|
|
238
|
+
"app.models.save": [
|
|
239
|
+
"ctrl+s"
|
|
240
|
+
],
|
|
241
|
+
"app.models.enableAll": [
|
|
242
|
+
"ctrl+a"
|
|
243
|
+
],
|
|
244
|
+
"app.models.clearAll": [
|
|
245
|
+
"ctrl+x"
|
|
246
|
+
],
|
|
247
|
+
"app.models.toggleProvider": [
|
|
248
|
+
"ctrl+p"
|
|
249
|
+
],
|
|
250
|
+
"app.models.reorderUp": [
|
|
251
|
+
"alt+up"
|
|
252
|
+
],
|
|
253
|
+
"app.models.reorderDown": [
|
|
254
|
+
"alt+down"
|
|
255
|
+
],
|
|
256
|
+
"app.tree.filter.default": [
|
|
257
|
+
"ctrl+d"
|
|
258
|
+
],
|
|
259
|
+
"app.tree.filter.noTools": [
|
|
260
|
+
"ctrl+t"
|
|
261
|
+
],
|
|
262
|
+
"app.tree.filter.userOnly": [
|
|
263
|
+
"ctrl+u"
|
|
264
|
+
],
|
|
265
|
+
"app.tree.filter.labeledOnly": [
|
|
266
|
+
"ctrl+l"
|
|
267
|
+
],
|
|
268
|
+
"app.tree.filter.all": [
|
|
269
|
+
"ctrl+a"
|
|
270
|
+
],
|
|
271
|
+
"app.tree.filter.cycleForward": [
|
|
272
|
+
"ctrl+o"
|
|
273
|
+
],
|
|
274
|
+
"app.tree.filter.cycleBackward": [
|
|
275
|
+
"shift+ctrl+o"
|
|
276
|
+
]
|
|
277
|
+
} as const;
|
|
278
|
+
export const ACTION_ALIASES: Readonly<Record<string,string>> = {
|
|
279
|
+
"tui.input.submit": "editor.submit",
|
|
280
|
+
"tui.input.newLine": "editor.newLine",
|
|
281
|
+
"tui.input.tab": "editor.complete",
|
|
282
|
+
"app.model.select": "app.model",
|
|
283
|
+
"app.model.cycleForward": "app.cycleModel",
|
|
284
|
+
"app.model.cycleBackward": "app.cycleModelReverse",
|
|
285
|
+
"app.thinking.cycle": "app.cycleThinking",
|
|
286
|
+
"app.thinking.toggle": "app.toggleThinking",
|
|
287
|
+
"app.tools.expand": "app.toggleTools",
|
|
288
|
+
"app.editor.external": "app.externalEditor",
|
|
289
|
+
"app.thinking.save": "app.saveDefault",
|
|
290
|
+
"tui.editor.cursorUp": "editor.cursorUp",
|
|
291
|
+
"tui.editor.cursorDown": "editor.cursorDown",
|
|
292
|
+
"tui.editor.cursorLeft": "editor.cursorLeft",
|
|
293
|
+
"tui.editor.cursorRight": "editor.cursorRight",
|
|
294
|
+
"tui.editor.cursorWordLeft": "editor.cursorWordLeft",
|
|
295
|
+
"tui.editor.cursorWordRight": "editor.cursorWordRight",
|
|
296
|
+
"tui.editor.cursorLineStart": "editor.cursorLineStart",
|
|
297
|
+
"tui.editor.cursorLineEnd": "editor.cursorLineEnd",
|
|
298
|
+
"tui.editor.deleteCharBackward": "editor.deleteCharBackward",
|
|
299
|
+
"tui.editor.deleteCharForward": "editor.deleteCharForward",
|
|
300
|
+
"tui.editor.deleteWordBackward": "editor.deleteWordBackward",
|
|
301
|
+
"tui.editor.deleteWordForward": "editor.deleteWordForward",
|
|
302
|
+
"tui.editor.deleteToLineStart": "editor.deleteToLineStart",
|
|
303
|
+
"tui.editor.deleteToLineEnd": "editor.deleteToLineEnd",
|
|
304
|
+
"tui.editor.yank": "editor.yank",
|
|
305
|
+
"tui.editor.undo": "editor.undo"
|
|
306
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Only an entire list of image paths is a drop; ordinary prose and shell syntax remain literal. */
|
|
2
|
+
export function droppedImagePaths(text: string): string[] | null {
|
|
3
|
+
const paths: string[] = [];
|
|
4
|
+
let token = "", quote = "", escaped = false;
|
|
5
|
+
for (const char of text.trim()) {
|
|
6
|
+
if (escaped) { token += char; escaped = false; }
|
|
7
|
+
else if (char === "\\" && quote !== "'") escaped = true;
|
|
8
|
+
else if (quote) { if (char === quote) quote = ""; else token += char; }
|
|
9
|
+
else if (char === "'" || char === '"') quote = char;
|
|
10
|
+
else if (/\s/.test(char)) { if (token) { paths.push(token); token = ""; } }
|
|
11
|
+
else token += char;
|
|
12
|
+
}
|
|
13
|
+
if (escaped || quote) return null;
|
|
14
|
+
if (token) paths.push(token);
|
|
15
|
+
return paths.length > 0 && paths.every(path => !/[\x00-\x1f]/.test(path) && !path.startsWith("@") &&
|
|
16
|
+
/\.(png|jpe?g|gif|webp|bmp)$/i.test(path) && !/[;$|<>`]/.test(path)) ? paths : null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { modelKey } from "./session-position.ts";
|
|
2
|
+
|
|
3
|
+
/** D13: identity and selection are independent of provider SDKs and source order. */
|
|
4
|
+
export function mergeCatalogs<T extends { provider: string; id: string }>(...layers: readonly (readonly T[])[]): T[] {
|
|
5
|
+
const models = new Map<string, T>();
|
|
6
|
+
for (const layer of layers) for (const model of layer) models.set(modelKey(model), model);
|
|
7
|
+
return [...models.values()];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Provider-local allow/deny patterns use literal characters plus '*' and '?'. */
|
|
11
|
+
export function matchesModelPattern(id: string, pattern: string): boolean {
|
|
12
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
13
|
+
return new RegExp(`^${escaped}$`, "u").test(id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function modelEnabled(id: string, allow?: readonly string[], deny?: readonly string[]): boolean {
|
|
17
|
+
return (allow === undefined || allow.some(pattern => matchesModelPattern(id, pattern)))
|
|
18
|
+
&& !(deny?.some(pattern => matchesModelPattern(id, pattern)) ?? false);
|
|
19
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { LogEvent, NodeRef } from "./events.ts";
|
|
3
3
|
import { isExecutionOutcome } from "./outcome.ts";
|
|
4
4
|
import { isJsonValue, isToolDiagnostic } from "./tool-contract.ts";
|
|
5
|
+
import { validImageRef } from "./images.ts";
|
|
5
6
|
|
|
6
7
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
7
8
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -23,11 +24,27 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
23
24
|
const carried = value["carried_from"];
|
|
24
25
|
if (carried !== undefined && !(isRecord(carried) && natural(carried["node"]) && typeof carried["event"] === "string")) return false;
|
|
25
26
|
const has = (...keys: string[]): boolean => keys.every((key) => typeof value[key] === "string");
|
|
27
|
+
const images = value["images"];
|
|
28
|
+
if (images !== undefined && (!Array.isArray(images) || !images.every(validImageRef))) return false;
|
|
26
29
|
switch (value["t"]) {
|
|
30
|
+
case "extension": return has("customType", "content") && typeof value["display"] === "boolean" && typeof value["inContext"] === "boolean" && (value["data"] === undefined || isJsonValue(value["data"]));
|
|
27
31
|
case "user": return has("text");
|
|
32
|
+
case "outcome": return has("reason") && isExecutionOutcome(value["outcome"]);
|
|
33
|
+
case "user_bash": {
|
|
34
|
+
const ref = value["content_ref"];
|
|
35
|
+
const outcome = value["outcome"];
|
|
36
|
+
if (!isExecutionOutcome(outcome) || !["tool_completed", "tool_failed", "cancelled", "timeout"].includes(outcome.reason)) return false;
|
|
37
|
+
if (outcome.reason === "tool_completed" && value["exit"] !== 0) return false;
|
|
38
|
+
if (outcome.reason === "tool_failed" && value["exit"] === 0) return false;
|
|
39
|
+
return has("command", "content") && Number.isInteger(value["exit"]) && typeof value["excludeFromContext"] === "boolean" &&
|
|
40
|
+
isRecord(ref) && typeof ref["sha256"] === "string" &&
|
|
41
|
+
typeof ref["path"] === "string" && natural(ref["bytes"]);
|
|
42
|
+
}
|
|
28
43
|
case "guidance": return has("text") && ["continuation", "repetition", "budget", "boundary", "stall", "output_recovery", "completion_review", "churn", "deadline"].includes(String(value["reason"])) && strings(value["sources"]);
|
|
29
44
|
case "interrupt": return has("user_id");
|
|
30
|
-
case "assistant": return (value["stop_reason"] === null || optionalString(value["stop_reason"])) &&
|
|
45
|
+
case "assistant": return (value["stop_reason"] === null || optionalString(value["stop_reason"])) &&
|
|
46
|
+
(value["replay_key"] === undefined || typeof value["replay_key"] === "string" && /^[a-f0-9]{64}$/.test(value["replay_key"])) &&
|
|
47
|
+
Array.isArray(value["blocks"]) && value["blocks"].every((b: unknown) => {
|
|
31
48
|
if (!isRecord(b)) return false;
|
|
32
49
|
if (b["type"] === "text") return typeof b["text"] === "string";
|
|
33
50
|
if (b["type"] === "thinking") return typeof b["text"] === "string" && "raw" in b;
|
|
@@ -54,7 +71,7 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
54
71
|
(effect["sha256_before"] === null || typeof effect["sha256_before"] === "string");
|
|
55
72
|
}
|
|
56
73
|
case "cut": return has("handoff_ref") && ["trigger", "hard_limit", "model_checkpoint"].includes(String(value["reason"])) &&
|
|
57
|
-
optionalString(value["retained_from"]) &&
|
|
74
|
+
optionalString(value["retained_from"]) && optionalString(value["next_context_ref"]) &&
|
|
58
75
|
(value["next_node"] === undefined || natural(value["next_node"])) && optionalString(value["next_model"]) &&
|
|
59
76
|
(value["handoff_tokens"] === undefined || (typeof value["handoff_tokens"] === "number" && Number.isFinite(value["handoff_tokens"]) && value["handoff_tokens"] > 0)) &&
|
|
60
77
|
(value["predecessor"] === null || validNodeRef(value["predecessor"]));
|
package/src/core/events.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type { ExecutionOutcome } from "./outcome.ts";
|
|
22
|
+
import type { ImageRef } from "./images.ts";
|
|
22
23
|
import type { JsonValue, ToolDiagnostic } from "./tool-contract.ts";
|
|
23
24
|
|
|
24
25
|
export type EventId = string;
|
|
@@ -78,6 +79,29 @@ export interface UserEvent {
|
|
|
78
79
|
id: EventId;
|
|
79
80
|
at: IsoTime;
|
|
80
81
|
text: string;
|
|
82
|
+
images?: readonly ImageRef[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** User-authorized shell execution is distinct from a model tool invocation (Pi !/!!). */
|
|
86
|
+
export interface UserBashEvent {
|
|
87
|
+
t: "user_bash";
|
|
88
|
+
id: EventId;
|
|
89
|
+
at: IsoTime;
|
|
90
|
+
command: string;
|
|
91
|
+
content: string;
|
|
92
|
+
exit: number;
|
|
93
|
+
excludeFromContext: boolean;
|
|
94
|
+
outcome: ExecutionOutcome;
|
|
95
|
+
content_ref: ArtifactRef;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A terminal execution fact; human history must not turn a truncated answer into success. */
|
|
99
|
+
export interface OutcomeEvent {
|
|
100
|
+
t: "outcome";
|
|
101
|
+
id: EventId;
|
|
102
|
+
at: IsoTime;
|
|
103
|
+
reason: string;
|
|
104
|
+
outcome: ExecutionOutcome;
|
|
81
105
|
}
|
|
82
106
|
|
|
83
107
|
/** A factual harness notice, appended once rather than rewriting the prompt (N.1). */
|
|
@@ -119,7 +143,7 @@ export interface InterruptEvent {
|
|
|
119
143
|
* what a UI renders.
|
|
120
144
|
*/
|
|
121
145
|
export type AssistantBlock =
|
|
122
|
-
| { type: "text"; text: string }
|
|
146
|
+
| { type: "text"; text: string; wire?: unknown }
|
|
123
147
|
| { type: "thinking"; text: string; raw: unknown }
|
|
124
148
|
/**
|
|
125
149
|
* `wire` is the provider's own per-call data beyond id, name and arguments
|
|
@@ -134,6 +158,8 @@ export interface AssistantEvent {
|
|
|
134
158
|
blocks: readonly AssistantBlock[];
|
|
135
159
|
/** N.12: provider-observed termination, never a claim of task completion. Legacy logs omit it. */
|
|
136
160
|
stop_reason?: string | null;
|
|
161
|
+
/** D8: opaque digest of the wire profile that produced signed blocks; no credentials. */
|
|
162
|
+
replay_key?: string;
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
export interface ToolResultEvent {
|
|
@@ -144,6 +170,7 @@ export interface ToolResultEvent {
|
|
|
144
170
|
ok: boolean;
|
|
145
171
|
/** What the model sees. Large payloads belong on disk, referenced by effect. */
|
|
146
172
|
content: string;
|
|
173
|
+
images?: readonly ImageRef[];
|
|
147
174
|
/** Digest of the complete observation, even when it fits inline in the log. */
|
|
148
175
|
content_sha256?: Sha256;
|
|
149
176
|
/** O.3: semantic observation digest excludes presentation metadata. */
|
|
@@ -170,6 +197,12 @@ export interface CutEvent {
|
|
|
170
197
|
/** Durable transition intent; recovery opens this exact successor. */
|
|
171
198
|
next_node?: number;
|
|
172
199
|
next_model?: string;
|
|
200
|
+
/** Paired with next_model so crash recovery preserves the selected provider. */
|
|
201
|
+
next_provider?: string;
|
|
202
|
+
/** The successor request effort is durable even if the adapter has not been installed. */
|
|
203
|
+
next_thinking?: string;
|
|
204
|
+
/** D6: resource reload activates this frozen context only in the successor. */
|
|
205
|
+
next_context_ref?: Sha256;
|
|
173
206
|
/** V2 carried-state budget, fixed in the log for identical replay. */
|
|
174
207
|
handoff_tokens?: number;
|
|
175
208
|
/** N.8: first retained event on the effective ancestry; no copied transcript. */
|
|
@@ -439,8 +472,18 @@ export interface EvidenceLinkEvent {
|
|
|
439
472
|
carried_from?: CarriedFrom;
|
|
440
473
|
}
|
|
441
474
|
|
|
475
|
+
/** Extension state is append-only data; only explicit messages enter either projection (D1). */
|
|
476
|
+
export interface ExtensionEvent {
|
|
477
|
+
t: "extension"; id: string; at: string; customType: string;
|
|
478
|
+
content: string; display: boolean; inContext: boolean;
|
|
479
|
+
data?: import("./tool-contract.ts").JsonValue;
|
|
480
|
+
}
|
|
481
|
+
|
|
442
482
|
export type LogEvent =
|
|
483
|
+
| ExtensionEvent
|
|
443
484
|
| UserEvent
|
|
485
|
+
| UserBashEvent
|
|
486
|
+
| OutcomeEvent
|
|
444
487
|
| GuidanceEvent
|
|
445
488
|
| InterruptEvent
|
|
446
489
|
| AssistantEvent
|
|
@@ -462,7 +505,7 @@ export type LogEvent =
|
|
|
462
505
|
| WorkStatusEvent;
|
|
463
506
|
|
|
464
507
|
export const HARNESS_EVENTS = [
|
|
465
|
-
"user", "guidance", "interrupt", "assistant", "tool_result", "cut", "snapshot", "rewind",
|
|
508
|
+
"user", "user_bash", "outcome", "guidance", "interrupt", "assistant", "tool_result", "cut", "snapshot", "rewind", "extension",
|
|
466
509
|
] as const;
|
|
467
510
|
export const MODEL_EVENTS = [
|
|
468
511
|
"claim", "decision", "pitfall", "blocked", "question", "next_action", "work_status", "evidence_link",
|
package/src/core/export.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { AssistantBlock, LogEvent } from "./events.ts";
|
|
15
15
|
import { assertNever } from "./exhaustive.ts";
|
|
16
|
+
import { imageDescription } from "./images.ts";
|
|
16
17
|
|
|
17
18
|
export interface HtmlMeta {
|
|
18
19
|
title: string;
|
|
@@ -90,8 +91,11 @@ function escapeHtml(s: string): string {
|
|
|
90
91
|
|
|
91
92
|
function eventHtml(e: LogEvent): string {
|
|
92
93
|
switch (e.t) {
|
|
94
|
+
case "extension": return e.display ? wrap("event", e.customType, e.at, e.content || JSON.stringify(e.data)) : "";
|
|
93
95
|
case "user":
|
|
94
|
-
return wrap("user", "user", e.at, e.text);
|
|
96
|
+
return wrap("user", "user", e.at, [e.text, ...(e.images ?? []).map(imageDescription)].join("\n"));
|
|
97
|
+
case "user_bash": return wrap("tool", e.excludeFromContext ? "user bash (excluded)" : "user bash", e.at, `$ ${e.command}\n${e.content}`);
|
|
98
|
+
case "outcome": return wrap("event", e.outcome.status, e.at, `${e.outcome.reason}: ${e.reason}`);
|
|
95
99
|
case "guidance":
|
|
96
100
|
return wrap("event", `harness ${e.reason}`, e.at, e.text);
|
|
97
101
|
case "assistant":
|
|
@@ -99,7 +103,7 @@ function eventHtml(e: LogEvent): string {
|
|
|
99
103
|
case "interrupt":
|
|
100
104
|
return wrap("event", "interrupt", e.at, `cancelled ${e.user_id}`);
|
|
101
105
|
case "tool_result":
|
|
102
|
-
return wrap("tool", `tool_result ${e.ok ? "ok" : "fail"}`, e.at, e.content);
|
|
106
|
+
return wrap("tool", `tool_result ${e.ok ? "ok" : "fail"}`, e.at, [e.content, ...(e.images ?? []).map(imageDescription)].join("\n"));
|
|
103
107
|
case "cut":
|
|
104
108
|
return wrap("event", "cut", e.at, `${e.reason} → ${e.handoff_ref}`);
|
|
105
109
|
case "snapshot":
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Typed extension flags are data; factories and the final CLI pass share their interpretation. */
|
|
2
|
+
export interface ExtensionFlag { type: "string" | "boolean"; description?: string; default?: string | boolean }
|
|
3
|
+
export interface FlagInput { value?: string; inline: boolean }
|
|
4
|
+
export function extensionFlagValue(name: string, definition: ExtensionFlag, input?: FlagInput): string | boolean | undefined {
|
|
5
|
+
if (!input) return definition.default;
|
|
6
|
+
if (definition.type === "string") {
|
|
7
|
+
if (input.value === undefined) throw new Error(`--${name} requires a string value`);
|
|
8
|
+
return input.value;
|
|
9
|
+
}
|
|
10
|
+
if (input.value === "true") return true;
|
|
11
|
+
if (input.value === "false") return false;
|
|
12
|
+
if (input.inline) throw new Error(`--${name} requires true or false`);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Immutable attachment metadata; D8 stores bytes once and projects references without I/O. */
|
|
2
|
+
export interface ImageRef {
|
|
3
|
+
readonly sha256: string;
|
|
4
|
+
readonly mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly bytes: number;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** Submission-time policy; enabling images later cannot rewrite an earlier request prefix. */
|
|
10
|
+
readonly blocked?: true;
|
|
11
|
+
readonly original?: { readonly sha256: string; readonly width: number; readonly height: number };
|
|
12
|
+
}
|
|
13
|
+
export interface ImageContent { readonly type: "image"; readonly image: ImageRef; readonly data?: string }
|
|
14
|
+
export type UserInput = string | { readonly text: string; readonly images?: readonly ImageRef[] };
|
|
15
|
+
export function validImageRef(value: unknown): value is ImageRef {
|
|
16
|
+
if (!value || typeof value !== "object") return false;
|
|
17
|
+
const v = value as Record<string, unknown>;
|
|
18
|
+
const digest = (s: unknown) => typeof s === "string" && /^[a-f0-9]{64}$/.test(s);
|
|
19
|
+
const positive = (n: unknown) => typeof n === "number" && Number.isSafeInteger(n) && n > 0;
|
|
20
|
+
if (v["blocked"] !== undefined && v["blocked"] !== true) return false;
|
|
21
|
+
if (!digest(v["sha256"]) || !positive(v["width"]) || !positive(v["height"]) || !positive(v["bytes"]) ||
|
|
22
|
+
typeof v["name"] !== "string" || !["image/png", "image/jpeg", "image/gif", "image/webp"].includes(String(v["mimeType"]))) return false;
|
|
23
|
+
if (v["original"] === undefined) return true;
|
|
24
|
+
if (!v["original"] || typeof v["original"] !== "object") return false;
|
|
25
|
+
const o = v["original"] as Record<string, unknown>;
|
|
26
|
+
return digest(o["sha256"]) && positive(o["width"]) && positive(o["height"]);
|
|
27
|
+
}
|
|
28
|
+
export function submissionImages(images: readonly ImageRef[], blocked: boolean): ImageRef[] {
|
|
29
|
+
return images.map(image => {
|
|
30
|
+
if (!validImageRef(image)) throw new Error("Invalid image reference");
|
|
31
|
+
const { sha256, mimeType, width, height, bytes, name, original } = image;
|
|
32
|
+
return { sha256, mimeType, width, height, bytes, name, ...(original ? { original } : {}), ...(blocked ? { blocked: true as const } : {}) };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function userInputData(input: UserInput, blocked?: boolean): { text: string; images?: readonly ImageRef[] } {
|
|
36
|
+
return typeof input === "string" ? { text: input } : { text: input.text,
|
|
37
|
+
...(input.images?.length ? { images: blocked === undefined ? input.images : submissionImages(input.images, blocked) } : {}) };
|
|
38
|
+
}
|
|
39
|
+
export function imageDescription(image: ImageRef): string {
|
|
40
|
+
const original = image.original;
|
|
41
|
+
return `[Image: ${image.name}, ${image.width}x${image.height}${original ? `; original ${original.width}x${original.height}, coordinate scale ${(original.width / image.width).toFixed(2)}` : ""}]`;
|
|
42
|
+
}
|
|
43
|
+
/** Assumption, not provider billing: 32px patches plus framing; anchors replace this after the response. */
|
|
44
|
+
export function estimateImageTokens(image: Pick<ImageRef, "width" | "height">): number {
|
|
45
|
+
return 256 + Math.ceil(image.width / 32) * Math.ceil(image.height / 32) * 2;
|
|
46
|
+
}
|
package/src/core/json-event.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type JsonLoopKind =
|
|
|
22
22
|
| "text_delta"
|
|
23
23
|
| "thinking_delta"
|
|
24
24
|
| "discard"
|
|
25
|
+
| "progress"
|
|
25
26
|
| "tool_call"
|
|
26
27
|
| "tool_result"
|
|
27
28
|
| "usage"
|
|
@@ -67,6 +68,7 @@ export function toJsonEvent(e: JsonLoopEvent, at: string): Record<string, unknow
|
|
|
67
68
|
case "text_delta":
|
|
68
69
|
case "thinking_delta":
|
|
69
70
|
case "discard":
|
|
71
|
+
case "progress":
|
|
70
72
|
return null;
|
|
71
73
|
case "assistant_text":
|
|
72
74
|
return { type: "assistant_text", text: e.text ?? "", at };
|