@visual-z/xeno 0.8.4 → 0.9.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/CHANGELOG.md +19 -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
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Open a stored image in the system viewer (docs/tasks/feat-tui-image-full-size.md).
|
|
3
|
-
*
|
|
4
|
-
* Blobs are content-addressed and have no extension, and viewers recognise a
|
|
5
|
-
* file by its extension, so the verified bytes are copied once to
|
|
6
|
-
* `<tmpdir>/xeno-images/<sha256>.<ext>` and that file is opened. The name is
|
|
7
|
-
* the hash: opening the same picture again reuses the file, and a file under
|
|
8
|
-
* that name can only hold those bytes because we never write it twice.
|
|
9
|
-
*/
|
|
10
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import { getBlobBytes } from "../store/blobs.ts";
|
|
14
|
-
import { MAX_IMAGE_SOURCE_BYTES } from "../core/image-limits.ts";
|
|
15
|
-
import { detectSupportedImageMimeType } from "../vendor/pi-rich/mime.ts";
|
|
16
|
-
|
|
17
|
-
const EXTENSIONS: Readonly<Record<string, string>> = { "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp", "image/bmp": "bmp" };
|
|
18
|
-
|
|
19
|
-
/** The platform's "open with the default application" command. */
|
|
20
|
-
function launchViewer(path: string): void {
|
|
21
|
-
Bun.spawn([process.platform === "darwin" ? "open" : "xdg-open", path], { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Opens the blob; returns why it could not, or null once the viewer was started. */
|
|
25
|
-
export async function openImageFile(blobDir: string, sha256: string,
|
|
26
|
-
options: { dir?: string; launch?: (path: string) => void } = {}): Promise<string | null> {
|
|
27
|
-
let bytes: Uint8Array | null;
|
|
28
|
-
try { bytes = await getBlobBytes(blobDir, sha256, MAX_IMAGE_SOURCE_BYTES); } catch (error) { return `Cannot open image: ${error instanceof Error ? error.message : String(error)}`; }
|
|
29
|
-
if (bytes === null) return "Cannot open image: it is no longer in the session's blob store";
|
|
30
|
-
const mime = detectSupportedImageMimeType(bytes);
|
|
31
|
-
const extension = mime === null ? undefined : EXTENSIONS[mime];
|
|
32
|
-
if (extension === undefined) return "Cannot open image: unrecognised image format";
|
|
33
|
-
const dir = options.dir ?? join(tmpdir(), "xeno-images");
|
|
34
|
-
const path = join(dir, `${sha256}.${extension}`);
|
|
35
|
-
try {
|
|
36
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
37
|
-
// `wx`: an existing file is a previous copy of these same bytes.
|
|
38
|
-
await writeFile(path, bytes, { flag: "wx", mode: 0o600 }).catch((error: NodeJS.ErrnoException) => { if (error.code !== "EEXIST") throw error; });
|
|
39
|
-
(options.launch ?? launchViewer)(path);
|
|
40
|
-
} catch (error) { return `Cannot open image: ${error instanceof Error ? error.message : String(error)}`; }
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI Responses API -- the second place, beside openai.ts, that knows an
|
|
3
|
-
* OpenAI wire format.
|
|
4
|
-
*
|
|
5
|
-
* It exists for one reason: reasoning that survives a turn. Chat Completions
|
|
6
|
-
* returns no reasoning for OpenAI's models, so every request starts the
|
|
7
|
-
* model's thinking from nothing; OpenAI measured the difference at 73.9% to
|
|
8
|
-
* 78.2% on Tau-Bench Retail (GPT-5 prompting guide). Responses returns each
|
|
9
|
-
* reasoning item with `encrypted_content` when `store` is false, and accepts
|
|
10
|
-
* it back unchanged.
|
|
11
|
-
*
|
|
12
|
-
* Stateless on purpose. `previous_response_id` would keep the conversation on
|
|
13
|
-
* OpenAI's servers -- a second truth beside the log that no projection could
|
|
14
|
-
* recompute. Instead the whole reasoning item goes into the thinking block's
|
|
15
|
-
* opaque raw, and the projection replays it like any other block.
|
|
16
|
-
*
|
|
17
|
-
* Event handling follows pi-mono `packages/ai/src/api/openai-responses-shared.ts`
|
|
18
|
-
* (MIT, 6f75515); the transport and SSE framing are this project's own.
|
|
19
|
-
*/
|
|
20
|
-
import type { AssistantBlock } from "../core/events.ts";
|
|
21
|
-
import type { ContentBlock, FrozenPrefix, Message } from "../core/project.ts";
|
|
22
|
-
import { imageData } from "./images.ts";
|
|
23
|
-
import { assertNever } from "../core/exhaustive.ts";
|
|
24
|
-
import type { OnDelta, Provider, ProviderConfig, ToolSchema, Turn } from "./provider.ts";
|
|
25
|
-
import { pushSse, HttpError, StreamError } from "./openai.ts";
|
|
26
|
-
import { withRetry, retryLabel, DEFAULT_RETRY, type RetryPolicy } from "./retry.ts";
|
|
27
|
-
import { ConnectionReuse } from "./connection-reuse.ts";
|
|
28
|
-
import { watchIdle, type IdleWatch } from "./stream-idle.ts";
|
|
29
|
-
|
|
30
|
-
import type { ProviderSendOptions } from "./provider-tool-policy.ts";
|
|
31
|
-
|
|
32
|
-
/** What this adapter parks in a thinking block's raw: the reasoning item, whole. */
|
|
33
|
-
interface ResponsesRaw { responses_item: Record<string, unknown> }
|
|
34
|
-
|
|
35
|
-
function responsesItemOf(raw: unknown): Record<string, unknown> | null {
|
|
36
|
-
if (typeof raw !== "object" || raw === null) return null;
|
|
37
|
-
const item = (raw as Partial<ResponsesRaw>).responses_item;
|
|
38
|
-
return typeof item === "object" && item !== null ? item : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type InputItem = Record<string, unknown>;
|
|
42
|
-
|
|
43
|
-
function inputItems(messages: readonly Message[]): InputItem[] {
|
|
44
|
-
const out: InputItem[] = [];
|
|
45
|
-
for (const message of messages) {
|
|
46
|
-
if (message.role === "assistant") {
|
|
47
|
-
const text: string[] = [];
|
|
48
|
-
const flushText = () => {
|
|
49
|
-
if (text.length === 0) return;
|
|
50
|
-
out.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: text.join("") }] });
|
|
51
|
-
text.length = 0;
|
|
52
|
-
};
|
|
53
|
-
for (const block of message.content) {
|
|
54
|
-
switch (block.type) {
|
|
55
|
-
case "thinking": {
|
|
56
|
-
// Another adapter's raw (a Completions reasoning string) is not a
|
|
57
|
-
// Responses item and cannot be sent as one; a model switch cuts,
|
|
58
|
-
// so this is the rare replay of a mixed log, not the common path.
|
|
59
|
-
const item = responsesItemOf(block.raw);
|
|
60
|
-
if (item !== null) { flushText(); out.push(item); }
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
case "text":
|
|
64
|
-
text.push(block.text);
|
|
65
|
-
break;
|
|
66
|
-
case "tool_use": {
|
|
67
|
-
flushText();
|
|
68
|
-
const wire = typeof block.wire === "object" && block.wire !== null ? block.wire as Record<string, unknown> : {};
|
|
69
|
-
out.push({
|
|
70
|
-
type: "function_call",
|
|
71
|
-
...(typeof wire["item_id"] === "string" ? { id: wire["item_id"] } : {}),
|
|
72
|
-
call_id: block.id, name: block.name, arguments: JSON.stringify(block.input),
|
|
73
|
-
});
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
case "tool_result":
|
|
77
|
-
case "image":
|
|
78
|
-
break;
|
|
79
|
-
default:
|
|
80
|
-
return assertNever(block, "responses assistant input");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
flushText();
|
|
84
|
-
} else {
|
|
85
|
-
const text: string[] = [];
|
|
86
|
-
const images: unknown[] = [];
|
|
87
|
-
for (const block of message.content as readonly ContentBlock[]) {
|
|
88
|
-
if (block.type === "text") text.push(block.text);
|
|
89
|
-
else if (block.type === "image") images.push({ type: "input_image", image_url: `data:${block.image.mimeType};base64,${imageData(block)}`, detail: "auto" });
|
|
90
|
-
else if (block.type === "tool_result") {
|
|
91
|
-
out.push({ type: "function_call_output", call_id: block.tool_use_id,
|
|
92
|
-
output: block.is_error ? `[error] ${block.content}` : block.content });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (text.length > 0 || images.length) out.push({ type: "message", role: "user", content: [...(text.length ? [{ type: "input_text", text: text.join("\n") }] : []), ...images] });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function responsesTools(tools: readonly unknown[]): unknown[] {
|
|
102
|
-
return (tools as ToolSchema[]).map((tool) => ({
|
|
103
|
-
type: "function", name: tool.name, description: tool.description, parameters: tool.input_schema,
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function buildResponsesRequest(config: ProviderConfig, frozen: FrozenPrefix, messages: readonly Message[]): Record<string, unknown> {
|
|
108
|
-
const spec = config.spec;
|
|
109
|
-
const request: Record<string, unknown> = {
|
|
110
|
-
model: spec.id,
|
|
111
|
-
instructions: frozen.system,
|
|
112
|
-
input: inputItems(messages),
|
|
113
|
-
store: false,
|
|
114
|
-
max_output_tokens: config.maxTokens,
|
|
115
|
-
};
|
|
116
|
-
if (frozen.tools.length > 0) request["tools"] = responsesTools(frozen.tools);
|
|
117
|
-
if (spec.reasoning) {
|
|
118
|
-
// Accepted but no longer required by current models; older ones need it.
|
|
119
|
-
if (config.effort !== "off" || spec.provider === "xai") request["include"] = ["reasoning.encrypted_content"];
|
|
120
|
-
const mapped = spec.thinkingLevelMap?.[config.effort];
|
|
121
|
-
if (mapped !== null && !(config.effort === "off" && spec.provider === "github-copilot")) {
|
|
122
|
-
request["reasoning"] = { effort: mapped ?? (config.effort === "off" ? "none" : config.effort), ...(config.effort === "off" ? {} : { summary: "auto" }) };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return request;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
interface Slot { kind: "thinking" | "text" | "call"; text: string; item?: Record<string, unknown> }
|
|
129
|
-
|
|
130
|
-
interface ResponseEnvelope {
|
|
131
|
-
status?: string;
|
|
132
|
-
incomplete_details?: { reason?: string } | null;
|
|
133
|
-
error?: { code?: string; message?: string } | null;
|
|
134
|
-
usage?: {
|
|
135
|
-
input_tokens?: number; output_tokens?: number;
|
|
136
|
-
input_tokens_details?: { cached_tokens?: number };
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function stopReasonOf(response: ResponseEnvelope, hasCalls: boolean): string | null {
|
|
141
|
-
switch (response.status) {
|
|
142
|
-
case "completed": return hasCalls ? "tool_use" : "end_turn";
|
|
143
|
-
// The loop's recovery keys on max_tokens; any other reason is not a
|
|
144
|
-
// completion and must not execute tools from the partial response.
|
|
145
|
-
case "incomplete": return response.incomplete_details?.reason === "max_output_tokens" ? "max_tokens" : `incomplete:${response.incomplete_details?.reason ?? "unknown"}`;
|
|
146
|
-
default: return response.status ?? null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function decodeArguments(raw: string): unknown {
|
|
151
|
-
if (raw.length === 0) return {};
|
|
152
|
-
try { return JSON.parse(raw); } catch { return { __unparsed_arguments: raw }; }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function readResponsesSse(body: ReadableStream<Uint8Array>, node: number, started: number, onDelta?: OnDelta): Promise<Turn> {
|
|
156
|
-
const slots = new Map<number, Slot>();
|
|
157
|
-
let final: ResponseEnvelope | undefined;
|
|
158
|
-
const handle = (event: Record<string, unknown>) => {
|
|
159
|
-
const index = typeof event["output_index"] === "number" ? event["output_index"] : -1;
|
|
160
|
-
const item = event["item"] as Record<string, unknown> | undefined;
|
|
161
|
-
switch (event["type"]) {
|
|
162
|
-
case "response.output_item.added": {
|
|
163
|
-
const kind = item?.["type"] === "reasoning" ? "thinking" : item?.["type"] === "function_call" ? "call" : item?.["type"] === "message" ? "text" : null;
|
|
164
|
-
if (kind !== null) slots.set(index, { kind, text: "", ...(item ? { item } : {}) });
|
|
165
|
-
if (kind === "call") onDelta?.({ kind: "tool", id: String(item?.["call_id"] ?? index), name: String(item?.["name"] ?? ""), arguments: "" });
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
case "response.reasoning_summary_text.delta":
|
|
169
|
-
case "response.reasoning_text.delta":
|
|
170
|
-
case "response.output_text.delta":
|
|
171
|
-
case "response.refusal.delta":
|
|
172
|
-
case "response.function_call_arguments.delta": {
|
|
173
|
-
const slot = slots.get(index);
|
|
174
|
-
const delta = typeof event["delta"] === "string" ? event["delta"] : "";
|
|
175
|
-
if (slot === undefined || delta.length === 0) return;
|
|
176
|
-
slot.text += delta;
|
|
177
|
-
if (slot.kind === "thinking") onDelta?.({ kind: "thinking", text: delta });
|
|
178
|
-
else if (slot.kind === "text") onDelta?.({ kind: "text", text: delta });
|
|
179
|
-
else onDelta?.({ kind: "tool", id: String(slot.item?.["call_id"] ?? index), name: String(slot.item?.["name"] ?? ""), arguments: slot.text });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
case "response.output_item.done": {
|
|
183
|
-
if (item === undefined) return;
|
|
184
|
-
const slot = slots.get(index) ?? { kind: item["type"] === "reasoning" ? "thinking" : item["type"] === "function_call" ? "call" : "text", text: "" } as Slot;
|
|
185
|
-
slot.item = item;
|
|
186
|
-
if (item["type"] === "function_call" && typeof item["arguments"] === "string") slot.text = item["arguments"];
|
|
187
|
-
if (item["type"] === "message" && Array.isArray(item["content"])) {
|
|
188
|
-
const parts = (item["content"] as Record<string, unknown>[]).map((c) => typeof c["text"] === "string" ? c["text"] : typeof c["refusal"] === "string" ? c["refusal"] : "");
|
|
189
|
-
if (parts.join("").length > 0) slot.text = parts.join("");
|
|
190
|
-
}
|
|
191
|
-
slots.set(index, slot);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
case "response.completed":
|
|
195
|
-
case "response.incomplete":
|
|
196
|
-
final = event["response"] as ResponseEnvelope;
|
|
197
|
-
return;
|
|
198
|
-
case "response.failed": {
|
|
199
|
-
const response = event["response"] as ResponseEnvelope | undefined;
|
|
200
|
-
const error = response?.error;
|
|
201
|
-
throw new StreamError(error ? `${error.code ?? "error"}: ${error.message ?? "no message"}` : "response failed without details", error?.code);
|
|
202
|
-
}
|
|
203
|
-
case "error":
|
|
204
|
-
throw new StreamError(`stream error: ${String(event["message"] ?? event["code"] ?? "unknown")}`,
|
|
205
|
-
typeof event["code"] === "string" ? event["code"] : undefined);
|
|
206
|
-
default:
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const reader = body.getReader();
|
|
212
|
-
const decoder = new TextDecoder();
|
|
213
|
-
let buffer = "";
|
|
214
|
-
try {
|
|
215
|
-
for (;;) {
|
|
216
|
-
const { value, done } = await reader.read();
|
|
217
|
-
const pushed = pushSse(buffer, decoder.decode(value ?? new Uint8Array(), { stream: !done }));
|
|
218
|
-
buffer = pushed.buffer;
|
|
219
|
-
for (const payload of pushed.payloads) if (payload.length > 0 && payload !== "[DONE]") handle(JSON.parse(payload));
|
|
220
|
-
if (done) break;
|
|
221
|
-
}
|
|
222
|
-
for (const payload of pushSse(buffer, "\n").payloads) if (payload.length > 0 && payload !== "[DONE]") handle(JSON.parse(payload));
|
|
223
|
-
} finally { reader.releaseLock(); }
|
|
224
|
-
if (final === undefined) throw new Error("responses stream ended before a terminal response event");
|
|
225
|
-
|
|
226
|
-
const blocks: AssistantBlock[] = [];
|
|
227
|
-
for (const [, slot] of [...slots.entries()].sort(([a], [b]) => a - b)) {
|
|
228
|
-
if (slot.kind === "thinking" && slot.item !== undefined) {
|
|
229
|
-
const summary = Array.isArray(slot.item["summary"])
|
|
230
|
-
? (slot.item["summary"] as Record<string, unknown>[]).map((s) => String(s["text"] ?? "")).join("\n\n") : "";
|
|
231
|
-
blocks.push({ type: "thinking", text: summary || slot.text, raw: { responses_item: slot.item } satisfies ResponsesRaw });
|
|
232
|
-
} else if (slot.kind === "text" && slot.text.length > 0) {
|
|
233
|
-
blocks.push({ type: "text", text: slot.text });
|
|
234
|
-
} else if (slot.kind === "call" && slot.item !== undefined) {
|
|
235
|
-
const callId = typeof slot.item["call_id"] === "string" ? slot.item["call_id"] : `call_${blocks.length}`;
|
|
236
|
-
blocks.push({ type: "tool_use", call_id: callId, name: String(slot.item["name"] ?? ""), input: decodeArguments(slot.text),
|
|
237
|
-
...(typeof slot.item["id"] === "string" ? { wire: { item_id: slot.item["id"] } } : {}) });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const usage = final.usage;
|
|
241
|
-
const cached = usage?.input_tokens_details?.cached_tokens ?? 0;
|
|
242
|
-
return {
|
|
243
|
-
blocks,
|
|
244
|
-
stopReason: stopReasonOf(final, blocks.some((block) => block.type === "tool_use")),
|
|
245
|
-
usage: {
|
|
246
|
-
// input_tokens includes the cached part; the uncached remainder is the difference.
|
|
247
|
-
input_tokens: Math.max(0, (usage?.input_tokens ?? 0) - cached),
|
|
248
|
-
output_tokens: usage?.output_tokens ?? 0,
|
|
249
|
-
cache_creation_input_tokens: 0,
|
|
250
|
-
cache_read_input_tokens: cached,
|
|
251
|
-
node, ms: Date.now() - started,
|
|
252
|
-
},
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export class OpenAIResponsesProvider implements Provider {
|
|
257
|
-
readonly config: ProviderConfig;
|
|
258
|
-
private readonly apiKey: string;
|
|
259
|
-
private readonly fetchImpl: typeof fetch;
|
|
260
|
-
private readonly connections = new ConnectionReuse();
|
|
261
|
-
private readonly retry: RetryPolicy;
|
|
262
|
-
private readonly onRetry: ((attempt: number, delayMs: number, error: unknown) => void) | undefined;
|
|
263
|
-
private readonly streamIdleMs: number | undefined;
|
|
264
|
-
|
|
265
|
-
constructor(config: ProviderConfig, apiKey: string, options: {
|
|
266
|
-
fetchImpl?: typeof fetch; retry?: RetryPolicy; onRetry?: (attempt: number, delayMs: number, error: unknown) => void;
|
|
267
|
-
/** Silence after which one attempt is abandoned; tests shorten it (stream-idle.ts). */
|
|
268
|
-
streamIdleMs?: number;
|
|
269
|
-
} = {}) {
|
|
270
|
-
this.config = config;
|
|
271
|
-
this.apiKey = apiKey;
|
|
272
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
273
|
-
this.retry = options.retry ?? DEFAULT_RETRY;
|
|
274
|
-
this.onRetry = options.onRetry;
|
|
275
|
-
this.streamIdleMs = options.streamIdleMs;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
send(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta, options?: ProviderSendOptions): Promise<Turn> {
|
|
279
|
-
return withRetry(() => watchIdle(signal, this.streamIdleMs, idle => this.attempt(frozen, messages, node, idle, onDelta, options)).catch((error: unknown) => { this.connections.note(error); throw error; }), this.retry, undefined,
|
|
280
|
-
(attempt, delayMs, error) => { onDelta?.({ kind: "discard" }); onDelta?.({ kind: "retry", attempt, delayMs, max: this.retry.maxRetries, reason: retryLabel(error) }); this.onRetry?.(attempt, delayMs, error); }, signal);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private async attempt(frozen: FrozenPrefix, messages: readonly Message[], node: number, idle: IdleWatch, onDelta?: OnDelta, options?: ProviderSendOptions): Promise<Turn> {
|
|
284
|
-
const started = Date.now();
|
|
285
|
-
const baseUrl = this.config.spec.baseUrl;
|
|
286
|
-
if (baseUrl === undefined) throw new Error(`model ${this.config.spec.id} has no baseUrl to send to`);
|
|
287
|
-
const init: RequestInit = {
|
|
288
|
-
method: "POST",
|
|
289
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${this.apiKey}` },
|
|
290
|
-
body: JSON.stringify({ ...buildResponsesRequest(this.config, frozen, messages), stream: true,
|
|
291
|
-
...(options?.toolUse === "disabled" ? { tool_choice: "none" } : {}) }),
|
|
292
|
-
};
|
|
293
|
-
init.signal = idle.signal;
|
|
294
|
-
const response = await this.fetchImpl(`${baseUrl.replace(/\/$/, "")}/responses`, this.connections.init(init));
|
|
295
|
-
if (!response.ok) {
|
|
296
|
-
const text = await response.text();
|
|
297
|
-
let detail = text.slice(0, 200);
|
|
298
|
-
try { const parsed = JSON.parse(text) as { error?: { message?: string } }; if (parsed.error?.message) detail = parsed.error.message; } catch { /* raw body */ }
|
|
299
|
-
throw new HttpError(response.status, response.headers, `HTTP ${response.status}: ${detail}`);
|
|
300
|
-
}
|
|
301
|
-
if (response.body === null) throw new Error(`${baseUrl} returned a streaming 200 with no body`);
|
|
302
|
-
return readResponsesSse(idle.body(response.body), node, started, onDelta);
|
|
303
|
-
}
|
|
304
|
-
}
|