rovecode 0.3.3 → 0.4.0-beta.2
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 +512 -0
- package/README.md +33 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-dybnr56b.js +2 -0
- package/dist/cli/ask-user-p8hq4xgj.js +2 -0
- package/dist/cli/auth-login-ewpgw5sm.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-xv3ypwev.js +9 -0
- package/dist/cli/catalog-737wb2s0.js +2 -0
- package/dist/cli/cli-arhg40m0.js +2 -0
- package/dist/cli/client-cf2pxx8q.js +2 -0
- package/dist/cli/commands-3p7e4xxs.js +2 -0
- package/dist/cli/connect-3q93d7cb.js +2 -0
- package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
- package/dist/cli/context-report-hbw9zfes.js +2 -0
- package/dist/cli/count-remote-mby98cd0.js +2 -0
- package/dist/cli/design-122y0axd.js +2 -0
- package/dist/cli/dispatch-b4egzvvh.js +2 -0
- package/dist/cli/doctor-x4jkv72e.js +3 -0
- package/dist/cli/executor-ftvg6tsy.js +2 -0
- package/dist/cli/export-pdgdhkch.js +2 -0
- package/dist/cli/files-cez9a96p.js +2 -0
- package/dist/cli/gauntlet-r3xxaszc.js +2 -0
- package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
- package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
- package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
- package/dist/cli/hashline-ewg5hbe3.js +2 -0
- package/dist/cli/http-n0kehsk8.js +5 -0
- package/dist/cli/index-z5qt1s76.js +2 -0
- package/dist/cli/install-80mp63kx.js +2 -0
- package/dist/cli/loop-12twjcat.js +2 -0
- package/dist/cli/main-01pv9206.js +4 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-1ztz6fkj.js +10 -0
- package/dist/cli/main-23q7cmww.js +9 -0
- package/dist/cli/main-2rzbexn2.js +3 -0
- package/dist/cli/main-2wyax8k9.js +9 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2z3dek0b.js +3 -0
- package/dist/cli/main-2zgsknth.js +3 -0
- package/dist/cli/main-45ejth3a.js +4 -0
- package/dist/cli/main-45rn3trk.js +22 -0
- package/dist/cli/main-4p4e2w7x.js +4 -0
- package/dist/cli/main-4y0tnfpa.js +16 -0
- package/dist/cli/main-5py0rkmc.js +4 -0
- package/dist/cli/main-6dtqmbt6.js +7 -0
- package/dist/cli/main-6h9x282m.js +4 -0
- package/dist/cli/main-6vjeds42.js +3 -0
- package/dist/cli/main-78gq4bt9.js +6 -0
- package/dist/cli/main-7jd5vh3x.js +4 -0
- package/dist/cli/main-7kt6r53y.js +4 -0
- package/dist/cli/main-8c1tbazx.js +58 -0
- package/dist/cli/main-9a9rnh47.js +19 -0
- package/dist/cli/main-9ht36z12.js +3 -0
- package/dist/cli/main-a2yfvcy9.js +7 -0
- package/dist/cli/main-a3f51n0x.js +5 -0
- package/dist/cli/main-b8zq261k.js +3 -0
- package/dist/cli/main-bxtvnf6d.js +13 -0
- package/dist/cli/main-edxc3yzt.js +4 -0
- package/dist/cli/main-evgz4mp5.js +21 -0
- package/dist/cli/main-f33fc5je.js +9 -0
- package/dist/cli/main-fvnpq46y.js +12 -0
- package/dist/cli/main-gbbty4d4.js +3 -0
- package/dist/cli/main-gth53dnt.js +25 -0
- package/dist/cli/main-hqbz10aw.js +9 -0
- package/dist/cli/main-hrrvcfan.js +38 -0
- package/dist/cli/main-hzwtsb2m.js +5 -0
- package/dist/cli/main-j7ttv0sd.js +34 -0
- package/dist/cli/main-jak598k9.js +5 -0
- package/dist/cli/main-kba6zeyd.js +6 -0
- package/dist/cli/main-kwwsz6rq.js +3 -0
- package/dist/cli/main-m8vm17zq.js +3 -0
- package/dist/cli/main-mg4f96e1.js +3 -0
- package/dist/cli/main-mg9b20ac.js +18 -0
- package/dist/cli/main-mgb9ccnx.js +3 -0
- package/dist/cli/main-mjt2p7aj.js +3 -0
- package/dist/cli/main-n6qrdbmy.js +3 -0
- package/dist/cli/main-na7wse0x.js +5 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-ntqef02r.js +10 -0
- package/dist/cli/main-nvc3yjay.js +136 -0
- package/dist/cli/main-p0cfn6nr.js +16 -0
- package/dist/cli/main-qj2djy17.js +19 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-qvarybsp.js +3 -0
- package/dist/cli/main-rebtt91r.js +5 -0
- package/dist/cli/main-rpg7h8mb.js +3 -0
- package/dist/cli/main-rsy72qmw.js +15 -0
- package/dist/cli/main-rvetps99.js +18 -0
- package/dist/cli/main-s4bb0jav.js +3 -0
- package/dist/cli/main-s9v8k74e.js +3 -0
- package/dist/cli/main-tjvwmscs.js +3 -0
- package/dist/cli/main-tkgarpjj.js +4 -0
- package/dist/cli/main-v8y60bb2.js +3 -0
- package/dist/cli/main-vhrrq337.js +3 -0
- package/dist/cli/main-vp2dfb7s.js +4 -0
- package/dist/cli/main-vqbr22sz.js +8 -0
- package/dist/cli/main-vxnwe5xx.js +18 -0
- package/dist/cli/main-wgph00xf.js +5 -0
- package/dist/cli/main-wk2csfnj.js +5 -0
- package/dist/cli/main-wm997zjx.js +3 -0
- package/dist/cli/main-wpkyraxh.js +3 -0
- package/dist/cli/main-wqt32p5x.js +4 -0
- package/dist/cli/main-x9ct6y1a.js +3 -0
- package/dist/cli/main-xfekqh9m.js +7 -0
- package/dist/cli/main-xt9zc3n6.js +7 -0
- package/dist/cli/main-xx2z3zh5.js +4 -0
- package/dist/cli/main-y5c82rxr.js +3 -0
- package/dist/cli/main-yrjt2sqt.js +14 -0
- package/dist/cli/main-ys6zj3yr.js +3 -0
- package/dist/cli/main-ywbxshqc.js +8 -0
- package/dist/cli/main-z13755t8.js +25 -0
- package/dist/cli/main-zc7pyrbj.js +4 -0
- package/dist/cli/main.js +279 -0
- package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
- package/dist/cli/mcp-login-bthtfpt7.js +2 -0
- package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
- package/dist/cli/notify-54v5z9dz.js +2 -0
- package/dist/cli/oauth-g5gme95c.js +2 -0
- package/dist/cli/output-satndjap.js +16 -0
- package/dist/cli/profiles-sfhpbq3m.js +2 -0
- package/dist/cli/provider-config-hv3xtdt4.js +2 -0
- package/dist/cli/provider-kwzq6g84.js +2 -0
- package/dist/cli/registry-fh0hdnyn.js +2 -0
- package/dist/cli/registry-y1y8e94r.js +2 -0
- package/dist/cli/repl-t4z03mqq.js +11 -0
- package/dist/cli/resume-fqt4chg8.js +2 -0
- package/dist/cli/run-flags-rysbag9t.js +2 -0
- package/dist/cli/runtime-j19fjbsa.js +2 -0
- package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
- package/dist/cli/server-r0b6bksk.js +5 -0
- package/dist/cli/session-arg-txmn5g4x.js +2 -0
- package/dist/cli/session-ed250d9j.js +2 -0
- package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
- package/dist/cli/settings-y9rzcqx8.js +2 -0
- package/dist/cli/setup-jmbr11j0.js +2 -0
- package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
- package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
- package/dist/cli/smoke-1bg937kx.js +8 -0
- package/dist/cli/start-chat-p01cdks3.js +12 -0
- package/dist/cli/stream-4wmyaypz.js +2 -0
- package/dist/cli/task-eg4s093s.js +2 -0
- package/dist/cli/tasks-12v9rr9k.js +2 -0
- package/dist/cli/thinking-a5ngvqyh.js +2 -0
- package/dist/cli/todo-1wxpcecx.js +2 -0
- package/dist/cli/tools-2ftsya7w.js +2 -0
- package/dist/cli/tools-x1tj4fxm.js +2 -0
- package/dist/cli/trust-cmd-hccxehzb.js +2 -0
- package/dist/cli/update-check-ygt3vd7m.js +2 -0
- package/dist/cli/update-cmd-v23qhr8c.js +2 -0
- package/dist/cli/voice-g1gtck92.js +2 -0
- package/dist/cli/webfetch-0nnrjgb5.js +2 -0
- package/dist/cli/websearch-f0vr2p7d.js +2 -0
- package/dist/cli/workspace-9rq1w4ta.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +6 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/rovecode.exe +4 -0
- package/package.json +83 -72
- package/bin/rovecode.ts +0 -21
- package/src/acp/server.ts +0 -374
- package/src/cli/auth-login.ts +0 -122
- package/src/cli/connect.ts +0 -244
- package/src/cli/context-cmd.ts +0 -199
- package/src/cli/dispatch.ts +0 -82
- package/src/cli/doctor.ts +0 -362
- package/src/cli/export.ts +0 -276
- package/src/cli/help.ts +0 -293
- package/src/cli/is-tui-invocation.ts +0 -8
- package/src/cli/main.ts +0 -583
- package/src/cli/market-cmd.ts +0 -658
- package/src/cli/mcp-login.ts +0 -141
- package/src/cli/mcp-market-cmd.ts +0 -302
- package/src/cli/output.ts +0 -382
- package/src/cli/repl.ts +0 -250
- package/src/cli/repomap-root.ts +0 -14
- package/src/cli/resume.ts +0 -57
- package/src/cli/run-flags.ts +0 -43
- package/src/cli/run-limits.ts +0 -78
- package/src/cli/runtime.ts +0 -931
- package/src/cli/session-arg.ts +0 -30
- package/src/cli/sessions-cmd.ts +0 -145
- package/src/cli/setup.ts +0 -153
- package/src/cli/skills-cmd.ts +0 -194
- package/src/cli/start-chat.ts +0 -65
- package/src/cli/trust-cmd.ts +0 -52
- package/src/coding/bash.ts +0 -148
- package/src/coding/checkpoints.ts +0 -327
- package/src/coding/diff.ts +0 -138
- package/src/coding/files.ts +0 -341
- package/src/coding/hashline.ts +0 -274
- package/src/coding/lsp-gate.ts +0 -254
- package/src/coding/lsp-servers.ts +0 -147
- package/src/coding/lsp.ts +0 -283
- package/src/coding/repomap-cache.ts +0 -99
- package/src/coding/repomap-files.ts +0 -192
- package/src/coding/repomap.ts +0 -481
- package/src/core/agents.ts +0 -255
- package/src/core/compaction.ts +0 -259
- package/src/core/config.ts +0 -289
- package/src/core/context-report.ts +0 -228
- package/src/core/context.ts +0 -60
- package/src/core/count-remote.ts +0 -107
- package/src/core/execpolicy-rules.ts +0 -196
- package/src/core/execpolicy.ts +0 -385
- package/src/core/executor.ts +0 -454
- package/src/core/guardrails.ts +0 -400
- package/src/core/hooks.ts +0 -411
- package/src/core/images.ts +0 -230
- package/src/core/intro.ts +0 -266
- package/src/core/loop.ts +0 -567
- package/src/core/modes.ts +0 -372
- package/src/core/orchestrator.ts +0 -245
- package/src/core/proc-group.ts +0 -48
- package/src/core/project-trust.ts +0 -98
- package/src/core/reflection.ts +0 -165
- package/src/core/sandbox-config.ts +0 -186
- package/src/core/session-id.ts +0 -24
- package/src/core/session-images.ts +0 -73
- package/src/core/session-ops.ts +0 -183
- package/src/core/session-text.ts +0 -29
- package/src/core/session.ts +0 -469
- package/src/core/settings.ts +0 -170
- package/src/core/tasks.ts +0 -646
- package/src/core/token-scale.ts +0 -108
- package/src/core/tools.ts +0 -309
- package/src/core/trust.ts +0 -104
- package/src/core/types.ts +0 -330
- package/src/core/update-check.ts +0 -171
- package/src/core/usage.ts +0 -204
- package/src/core/validate.ts +0 -121
- package/src/core/verify-gate.ts +0 -159
- package/src/core/verify.ts +0 -236
- package/src/core/voice.ts +0 -158
- package/src/core/win-job.ts +0 -183
- package/src/core/workspace.ts +0 -184
- package/src/design/audit.ts +0 -797
- package/src/design/direction.ts +0 -190
- package/src/design/rules.ts +0 -157
- package/src/eval/bench.ts +0 -150
- package/src/eval/gauntlet-runner.ts +0 -215
- package/src/eval/gauntlet-support.ts +0 -84
- package/src/eval/gauntlet-wave3.ts +0 -269
- package/src/eval/gauntlet-wave4.ts +0 -217
- package/src/eval/gauntlet.ts +0 -253
- package/src/index.ts +0 -17
- package/src/lanes/agy.ts +0 -95
- package/src/lanes/approval.ts +0 -24
- package/src/lanes/claude.ts +0 -129
- package/src/lanes/codex.ts +0 -127
- package/src/lanes/events.ts +0 -130
- package/src/lanes/job.ts +0 -142
- package/src/lanes/opencode.ts +0 -122
- package/src/lanes/process.ts +0 -184
- package/src/lanes/progress.ts +0 -183
- package/src/lanes/registry.ts +0 -178
- package/src/lanes/runner.ts +0 -124
- package/src/lanes/types.ts +0 -112
- package/src/market/catalogs/mcp-docs.json +0 -111
- package/src/market/catalogs/plugins.json +0 -111
- package/src/market/catalogs/skills.json +0 -478
- package/src/market/clone.ts +0 -72
- package/src/market/context-cost.ts +0 -121
- package/src/market/digest.ts +0 -106
- package/src/market/index.ts +0 -22
- package/src/market/install.ts +0 -578
- package/src/market/manifest.ts +0 -187
- package/src/market/prereq.ts +0 -145
- package/src/market/registry.ts +0 -363
- package/src/market/resolve.ts +0 -111
- package/src/market/types.ts +0 -236
- package/src/market/validate.ts +0 -227
- package/src/mcp/client.ts +0 -449
- package/src/mcp/config.ts +0 -252
- package/src/mcp/local-package.ts +0 -211
- package/src/mcp/market-catalog.ts +0 -84
- package/src/mcp/market-install.ts +0 -289
- package/src/mcp/market.ts +0 -362
- package/src/mcp/oauth.ts +0 -251
- package/src/mcp/prompts-resources.ts +0 -249
- package/src/mcp/shared.ts +0 -149
- package/src/mcp/status.ts +0 -67
- package/src/mcp/tools.ts +0 -275
- package/src/mcp/transport.ts +0 -122
- package/src/mcp/trust.ts +0 -25
- package/src/memory/blocks.ts +0 -278
- package/src/memory/recall.ts +0 -355
- package/src/memory/scope.ts +0 -182
- package/src/memory/store.ts +0 -105
- package/src/memory/tools.ts +0 -99
- package/src/plugins/cli.ts +0 -119
- package/src/plugins/discover.ts +0 -108
- package/src/plugins/index.ts +0 -50
- package/src/plugins/install.ts +0 -184
- package/src/plugins/load.ts +0 -124
- package/src/plugins/manifest.ts +0 -92
- package/src/plugins/state.ts +0 -83
- package/src/providers/auth.ts +0 -408
- package/src/providers/cache.ts +0 -223
- package/src/providers/catalog-local.ts +0 -160
- package/src/providers/catalog.ts +0 -421
- package/src/providers/middleware-context.ts +0 -86
- package/src/providers/middleware.ts +0 -373
- package/src/providers/model-list.ts +0 -23
- package/src/providers/oauth/common.ts +0 -105
- package/src/providers/oauth/device-code.ts +0 -107
- package/src/providers/oauth/github-copilot.ts +0 -146
- package/src/providers/oauth/loopback.ts +0 -158
- package/src/providers/oauth/openai.ts +0 -163
- package/src/providers/oauth/openrouter.ts +0 -89
- package/src/providers/oauth/pkce.ts +0 -45
- package/src/providers/oauth/registry.ts +0 -39
- package/src/providers/oauth/seam.ts +0 -89
- package/src/providers/profile-glm53.ts +0 -111
- package/src/providers/profile-sonnet5-persona.ts +0 -65
- package/src/providers/profile-sonnet5-voice.ts +0 -23
- package/src/providers/profiles.ts +0 -156
- package/src/providers/provider-config.ts +0 -311
- package/src/providers/registry.ts +0 -333
- package/src/providers/responses.ts +0 -209
- package/src/providers/retry.ts +0 -234
- package/src/providers/router.ts +0 -294
- package/src/providers/sse.ts +0 -26
- package/src/providers/stream-errors.ts +0 -117
- package/src/providers/stream.ts +0 -566
- package/src/providers/thinking.ts +0 -189
- package/src/providers/wire-messages.ts +0 -129
- package/src/providers/wire-responses.ts +0 -79
- package/src/providers/wire-select.ts +0 -53
- package/src/server/http.ts +0 -291
- package/src/server/openapi.ts +0 -246
- package/src/sextant/card-hits.ts +0 -102
- package/src/sextant/card-keys.ts +0 -55
- package/src/sextant/context-source.ts +0 -157
- package/src/sextant/crew-cards.ts +0 -350
- package/src/sextant/draw-agents.ts +0 -273
- package/src/sextant/draw-code.ts +0 -388
- package/src/sextant/draw-context.ts +0 -222
- package/src/sextant/draw-frame.ts +0 -164
- package/src/sextant/draw-market.ts +0 -573
- package/src/sextant/draw-messages.ts +0 -386
- package/src/sextant/draw-pet.ts +0 -230
- package/src/sextant/draw-plan.ts +0 -187
- package/src/sextant/draw-tabs.ts +0 -85
- package/src/sextant/draw-util.ts +0 -65
- package/src/sextant/draw-wizard.ts +0 -378
- package/src/sextant/engine.ts +0 -230
- package/src/sextant/frame-hits.ts +0 -25
- package/src/sextant/frame.ts +0 -101
- package/src/sextant/git-status.ts +0 -197
- package/src/sextant/grid.ts +0 -59
- package/src/sextant/input.ts +0 -119
- package/src/sextant/keys.ts +0 -521
- package/src/sextant/layout.ts +0 -86
- package/src/sextant/local-commands.ts +0 -169
- package/src/sextant/market-source.ts +0 -287
- package/src/sextant/mentions.ts +0 -200
- package/src/sextant/message-hits.ts +0 -26
- package/src/sextant/model.ts +0 -387
- package/src/sextant/overlays.ts +0 -456
- package/src/sextant/panel-hits.ts +0 -38
- package/src/sextant/pet.ts +0 -399
- package/src/sextant/screen.ts +0 -324
- package/src/sextant/scroll-hits.ts +0 -66
- package/src/sextant/scrollbar.ts +0 -82
- package/src/sextant/sextant-bridge.ts +0 -174
- package/src/sextant/sextant-cards.ts +0 -142
- package/src/sextant/sextant-diff-base.ts +0 -63
- package/src/sextant/sextant-files.ts +0 -154
- package/src/sextant/sextant-frame-loop.ts +0 -335
- package/src/sextant/sextant-renderer.ts +0 -574
- package/src/sextant/sextant-repo.ts +0 -140
- package/src/sextant/theme.ts +0 -66
- package/src/sextant/tool-rows.ts +0 -189
- package/src/sextant/types.ts +0 -493
- package/src/skills/index.ts +0 -387
- package/src/skills/pack.ts +0 -220
- package/src/skills/spec.ts +0 -162
- package/src/skills/tools.ts +0 -69
- package/src/skills/versioned.ts +0 -227
- package/src/telemetry/otel-export.ts +0 -122
- package/src/telemetry/otel-lanes.ts +0 -89
- package/src/telemetry/otel-logs.ts +0 -131
- package/src/telemetry/otel-metrics.ts +0 -136
- package/src/telemetry/otel.ts +0 -397
- package/src/telemetry/otlp.ts +0 -76
- package/src/tools/ask-user.ts +0 -156
- package/src/tools/bash-bg.ts +0 -94
- package/src/tools/bash-jobs.ts +0 -237
- package/src/tools/design.ts +0 -151
- package/src/tools/evalcell.ts +0 -338
- package/src/tools/html-text.ts +0 -139
- package/src/tools/provider.ts +0 -149
- package/src/tools/task.ts +0 -250
- package/src/tools/todo.ts +0 -320
- package/src/tools/webfetch.ts +0 -332
- package/src/tools/websearch.ts +0 -359
- package/src/tui/agents-cmd.ts +0 -41
- package/src/tui/app.ts +0 -749
- package/src/tui/attach.ts +0 -127
- package/src/tui/boot-notes.ts +0 -41
- package/src/tui/builtin-prompts.ts +0 -59
- package/src/tui/checkpoints-cmd.ts +0 -70
- package/src/tui/clipboard-image.ts +0 -81
- package/src/tui/clipboard.ts +0 -78
- package/src/tui/commands.ts +0 -283
- package/src/tui/config-view.ts +0 -53
- package/src/tui/context-cmds.ts +0 -282
- package/src/tui/cost.ts +0 -108
- package/src/tui/crash-guard.ts +0 -173
- package/src/tui/focus-terminal.ts +0 -34
- package/src/tui/git-cmds.ts +0 -273
- package/src/tui/git-plain.ts +0 -58
- package/src/tui/info-cmd.ts +0 -150
- package/src/tui/input-plain.ts +0 -76
- package/src/tui/mcp-cmd.ts +0 -128
- package/src/tui/memory-note.ts +0 -77
- package/src/tui/modes-cmd.ts +0 -45
- package/src/tui/notify-seq.ts +0 -100
- package/src/tui/notify.ts +0 -318
- package/src/tui/overlays.ts +0 -97
- package/src/tui/pi-renderer.ts +0 -428
- package/src/tui/providers-cmd.ts +0 -377
- package/src/tui/reasoning-view.ts +0 -56
- package/src/tui/renderer.ts +0 -128
- package/src/tui/replay-marker.ts +0 -29
- package/src/tui/session-cmd.ts +0 -148
- package/src/tui/session-manage.ts +0 -95
- package/src/tui/sextant-attach.ts +0 -102
- package/src/tui/sextant-io.ts +0 -202
- package/src/tui/sextant-smoke.ts +0 -110
- package/src/tui/shell-cmd.ts +0 -158
- package/src/tui/smoke.ts +0 -72
- package/src/tui/staged-terminal.ts +0 -50
- package/src/tui/startup.ts +0 -12
- package/src/tui/theme.ts +0 -59
- package/src/tui/todo-label.ts +0 -7
- package/src/tui/trust-card.ts +0 -107
- package/src/tui/tui-commands.ts +0 -87
- package/tsconfig.json +0 -30
- package/vendor/pi-tui/LICENSE +0 -21
- package/vendor/pi-tui/PATCHES.md +0 -12
- package/vendor/pi-tui/PROVENANCE.md +0 -12
- package/vendor/pi-tui/README.upstream.md +0 -854
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
- package/vendor/pi-tui/src/autocomplete.ts +0 -827
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
- package/vendor/pi-tui/src/components/box.ts +0 -138
- package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
- package/vendor/pi-tui/src/components/editor.ts +0 -2364
- package/vendor/pi-tui/src/components/h-stack.ts +0 -45
- package/vendor/pi-tui/src/components/image.ts +0 -128
- package/vendor/pi-tui/src/components/input.ts +0 -448
- package/vendor/pi-tui/src/components/loader.ts +0 -93
- package/vendor/pi-tui/src/components/markdown.ts +0 -1016
- package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
- package/vendor/pi-tui/src/components/select-list.ts +0 -230
- package/vendor/pi-tui/src/components/settings-list.ts +0 -277
- package/vendor/pi-tui/src/components/spacer.ts +0 -29
- package/vendor/pi-tui/src/components/stack.ts +0 -155
- package/vendor/pi-tui/src/components/text.ts +0 -108
- package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
- package/vendor/pi-tui/src/components/v-stack.ts +0 -34
- package/vendor/pi-tui/src/editor-component.ts +0 -75
- package/vendor/pi-tui/src/fuzzy.ts +0 -138
- package/vendor/pi-tui/src/index.ts +0 -149
- package/vendor/pi-tui/src/keybindings.ts +0 -321
- package/vendor/pi-tui/src/keys.ts +0 -1402
- package/vendor/pi-tui/src/kill-ring.ts +0 -47
- package/vendor/pi-tui/src/latex.ts +0 -1381
- package/vendor/pi-tui/src/layout-node.ts +0 -52
- package/vendor/pi-tui/src/layout.ts +0 -411
- package/vendor/pi-tui/src/native-modifiers.ts +0 -60
- package/vendor/pi-tui/src/native-module-path.ts +0 -32
- package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
- package/vendor/pi-tui/src/terminal-colors.ts +0 -74
- package/vendor/pi-tui/src/terminal-image.ts +0 -701
- package/vendor/pi-tui/src/terminal.ts +0 -554
- package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
- package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
- package/vendor/pi-tui/src/tui.ts +0 -1264
- package/vendor/pi-tui/src/undo-stack.ts +0 -29
- package/vendor/pi-tui/src/utils.ts +0 -1327
- package/vendor/pi-tui/src/word-navigation.ts +0 -118
- package/vendor/pi-tui/test/test-themes.ts +0 -39
- package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
- /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/providers/stream.ts
DELETED
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
/** Providers: StreamFn adapters (ADR-003 seam). Errors cross as stopReason "error", never throws.
|
|
2
|
-
*
|
|
3
|
-
* Wire protocols supported:
|
|
4
|
-
* - OpenAI-compatible /chat/completions (JSON + SSE streaming), with tools
|
|
5
|
-
* - Anthropic /messages (x-api-key), with tools
|
|
6
|
-
* - Any custom base URL + key (ROVECODE_BASE_URL/ROVECODE_API_KEY or explicit config)
|
|
7
|
-
* Model catalogs are FETCHED from the endpoint (/v1/models) — never hard-coded (pi pattern).
|
|
8
|
-
* Error-turn shaping (abort vs error; HTTP status + Retry-After side-channel, port #23) lives in stream-errors.ts.
|
|
9
|
-
* Message lowering (harness parts → wire content, incl. port #34 image blocks) lives in wire-messages.ts. */
|
|
10
|
-
|
|
11
|
-
import type { StreamFn, Message, AssistantTurn, StreamEvent, ModelRef, StopReason } from "../core/types.ts";
|
|
12
|
-
import { partsText } from "../core/loop.ts";
|
|
13
|
-
import { applyAnthropicCacheBoundaries } from "./cache.ts";
|
|
14
|
-
import { normalizeUsage } from "../core/usage.ts";
|
|
15
|
-
import { BUILTIN_PROVIDERS, buildSnapshot, pickDefault } from "./provider-config.ts";
|
|
16
|
-
import { failedTurn, fetchFirstByte, httpErrorTurn } from "./stream-errors.ts";
|
|
17
|
-
import { supportsImages } from "./catalog.ts";
|
|
18
|
-
import { profileWire } from "./profiles.ts";
|
|
19
|
-
import { anthropicThinking, thinkingBudget, thinkingPlan, type AnthropicThinkingShape } from "./thinking.ts";
|
|
20
|
-
import { toOpenAiMessages, toAnthropicMessages, toOpenAiToolSchemas, asToolSchema, type WireOptions } from "./wire-messages.ts";
|
|
21
|
-
import { sseLines } from "./sse.ts";
|
|
22
|
-
import { oauthStream, resolveOAuthProviderConfig, type OAuthSeamDeps } from "./oauth/seam.ts";
|
|
23
|
-
import { openaiResponsesStream } from "./responses.ts"; // the /responses wire (aion port #75)
|
|
24
|
-
import { openAiWire } from "./wire-select.ts"; // chat vs responses, per call
|
|
25
|
-
|
|
26
|
-
export { toOpenAiMessages, toAnthropicMessages, toOpenAiToolSchemas } from "./wire-messages.ts";
|
|
27
|
-
|
|
28
|
-
/** port #34: image parts go on the wire as image blocks unless the models.dev catalog says the
|
|
29
|
-
* model has no image input (then wire-messages.ts substitutes a text placeholder); an unknown
|
|
30
|
-
* model is given the image (catalog.ts supportsImages). */
|
|
31
|
-
const wireOptions = (model: ModelRef): WireOptions => ({ vision: supportsImages(model) !== false });
|
|
32
|
-
|
|
33
|
-
export interface ProviderConfig {
|
|
34
|
-
id: string; // provider id, e.g. "kaesra"
|
|
35
|
-
baseUrl: string; // e.g. https://api.kaesra.tech/v1
|
|
36
|
-
apiKey: string;
|
|
37
|
-
protocol: "openai" | "anthropic";
|
|
38
|
-
defaultModel?: string;
|
|
39
|
-
/** extra request headers (proxies, org ids) — providers.json `headers`; the protocol's own auth headers win.
|
|
40
|
-
* Also the OAuth providers' own headers (GitHub Copilot's editor headers, OpenAI's account id) */
|
|
41
|
-
headers?: Record<string, string>;
|
|
42
|
-
/** apiKey is a stored OAuth access token — providerStream/providerStreaming refresh it per request (oauth/seam.ts) */
|
|
43
|
-
oauth?: true;
|
|
44
|
-
/** pin the OpenAI wire for every request (the OpenAI OAuth token sets "responses" — a ChatGPT token is accepted by
|
|
45
|
-
* the Codex Responses backend only); unset = ROVECODE_OPENAI_WIRE, else the catalog rule (wire-select.ts) */
|
|
46
|
-
wire?: "responses" | "chat";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** What a wire adapter needs: endpoint root, key, optional extra headers (ProviderConfig.headers). */
|
|
50
|
-
export interface AdapterOptions {
|
|
51
|
-
baseUrl: string;
|
|
52
|
-
apiKey: string;
|
|
53
|
-
headers?: Record<string, string>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface ModelCatalogEntry {
|
|
57
|
-
id: string;
|
|
58
|
-
ownedBy?: string;
|
|
59
|
-
type?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ---------- catalog (fetched, cached) ----------
|
|
63
|
-
|
|
64
|
-
const catalogCache = new Map<string, { models: ModelCatalogEntry[]; fetchedAt: number }>();
|
|
65
|
-
const CATALOG_TTL_MS = 5 * 60_000;
|
|
66
|
-
|
|
67
|
-
export async function fetchModels(cfg: ProviderConfig, force = false): Promise<ModelCatalogEntry[]> {
|
|
68
|
-
const hit = catalogCache.get(cfg.id);
|
|
69
|
-
if (!force && hit && Date.now() - hit.fetchedAt < CATALOG_TTL_MS) return hit.models;
|
|
70
|
-
const url = cfg.baseUrl.replace(/\/$/, "") + "/models";
|
|
71
|
-
let models: ModelCatalogEntry[] = [];
|
|
72
|
-
try {
|
|
73
|
-
const res = await fetch(url, { headers: authHeaders(cfg) });
|
|
74
|
-
if (res.ok) {
|
|
75
|
-
const json = (await res.json()) as { data?: { id: string; owned_by?: string; type?: string }[] };
|
|
76
|
-
models = (json.data ?? []).map((m) => ({ id: m.id, ownedBy: m.owned_by, type: m.type }));
|
|
77
|
-
}
|
|
78
|
-
} catch { /* errors are empty catalog — provider seam never throws */ }
|
|
79
|
-
catalogCache.set(cfg.id, { models, fetchedAt: Date.now() });
|
|
80
|
-
return models;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function authHeaders(cfg: ProviderConfig): Record<string, string> {
|
|
84
|
-
return {
|
|
85
|
-
...(cfg.headers ?? {}),
|
|
86
|
-
...(cfg.protocol === "anthropic"
|
|
87
|
-
? { "x-api-key": cfg.apiKey, "anthropic-version": "2023-06-01" }
|
|
88
|
-
: { authorization: `Bearer ${cfg.apiKey}` }),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---------- factories ----------
|
|
93
|
-
|
|
94
|
-
const optsOf = (cfg: ProviderConfig): AdapterOptions => ({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, ...(cfg.headers !== undefined ? { headers: cfg.headers } : {}) });
|
|
95
|
-
|
|
96
|
-
/** `oauth`: the seam's injectable clock/fetch/store/provider list (tests drive a fake endpoint); production passes nothing */
|
|
97
|
-
export function providerStream(cfg: ProviderConfig, oauth: OAuthSeamDeps = {}): StreamFn {
|
|
98
|
-
if (cfg.oauth) return oauthStream(cfg, openAiWire(cfg, openaiCompatStream, openaiResponsesStream), oauth); // a stored OAuth token: refresh-once-if-expired, then the wire picked per call
|
|
99
|
-
return cfg.protocol === "anthropic" ? anthropicStream(optsOf(cfg)) : openAiWire(cfg, openaiCompatStream, openaiResponsesStream)(optsOf(cfg));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** The streaming twin of providerStream: text_delta as the model writes, the same final turn.
|
|
103
|
-
* This is what every surface gets by default — the one-shot JSON adapters above are the fallback.
|
|
104
|
-
* The ONE builder the CLI's SSE sites use, so an OAuth config's headers (GitHub Copilot's editor
|
|
105
|
-
* headers) reach the streaming request too; a headerless `openaiCompatStreaming({ baseUrl, apiKey })`
|
|
106
|
-
* at a call site is the bug test/unit/auth-login-sse.test.ts pins against. */
|
|
107
|
-
export function providerStreaming(cfg: ProviderConfig, oauth: OAuthSeamDeps = {}): StreamFn {
|
|
108
|
-
if (cfg.oauth) return oauthStream(cfg, openAiWire(cfg, openaiCompatStreaming, openaiResponsesStream), oauth); // the TUI's default path: a long session outlives a 30-minute Copilot token, so it refreshes here too
|
|
109
|
-
return cfg.protocol === "anthropic" ? anthropicStreaming(optsOf(cfg)) : openAiWire(cfg, openaiCompatStreaming, openaiResponsesStream)(optsOf(cfg)); // the Responses wire is SSE-only, so it is the same adapter on both factories
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Streaming is ON unless ROVECODE_STREAM says otherwise: `off`/`json`/`0`/`false`/`none` fall back to
|
|
113
|
-
* the one-shot JSON adapters (a proxy with no SSE route, a recording harness that wants one body).
|
|
114
|
-
* `sse` is still accepted — it used to be the opt-IN spelling and some scripts still set it. */
|
|
115
|
-
export function wantsStreaming(env: { ROVECODE_STREAM?: string | undefined }): boolean {
|
|
116
|
-
const v = (env.ROVECODE_STREAM ?? "").trim().toLowerCase();
|
|
117
|
-
return !(v === "off" || v === "json" || v === "0" || v === "false" || v === "none");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Streaming requests ask for an UNCOMPRESSED body. Measured against the Anthropic endpoint: with the
|
|
121
|
-
* default `accept-encoding: gzip, deflate, br`, the decompressor holds the whole SSE stream and every
|
|
122
|
-
* event lands in one burst at the end (first text delta at 11.1 s of an 11.1 s response); with
|
|
123
|
-
* `identity` the same prompt starts writing at 1.4 s. Streaming is the point of these two adapters, so
|
|
124
|
-
* the extra bytes are the right trade — the JSON adapters below keep compression. */
|
|
125
|
-
const SSE_HEADERS = { "accept-encoding": "identity" } as const;
|
|
126
|
-
|
|
127
|
-
/** A proxy that ignores `stream: true` and answers with one JSON body would otherwise leave the SSE
|
|
128
|
-
* reader with nothing to parse and the turn empty. Content-type decides: an explicit application/json
|
|
129
|
-
* is read whole through the same parser the one-shot adapters use (no deltas — there is nothing to
|
|
130
|
-
* stream), anything else is treated as an event stream. */
|
|
131
|
-
const isJsonBody = (res: Response): boolean => (res.headers.get("content-type") ?? "").includes("application/json");
|
|
132
|
-
|
|
133
|
-
/** Thinking, per protocol.
|
|
134
|
-
*
|
|
135
|
-
* Anthropic has TWO request shapes and no model exposes both reliably — measured against the live
|
|
136
|
-
* API on 2026-09-03:
|
|
137
|
-
*
|
|
138
|
-
* model output_config.effort thinking.type=enabled
|
|
139
|
-
* claude-opus-5 200 400 "not supported for this model"
|
|
140
|
-
* claude-sonnet-5 200 400
|
|
141
|
-
* claude-opus-4-5 200 200
|
|
142
|
-
* claude-sonnet-4-5 400 "does not support…" 200 → [thinking, text]
|
|
143
|
-
* claude-haiku-4-5 400 200
|
|
144
|
-
*
|
|
145
|
-
* So the shape is a property of the model, and guessing wrong is a hard 400, not a downgrade. There
|
|
146
|
-
* is no capability field to read (the /models list carries ids only), and a hard-coded table would
|
|
147
|
-
* rot with the next release — this file's rule is that catalogs are fetched, never hard-coded. So:
|
|
148
|
-
* try the newer shape, and when the endpoint says that shape is unsupported, flip and retry ONCE,
|
|
149
|
-
* remembering the answer per model id for the rest of the process. One wasted round trip the first
|
|
150
|
-
* time a model is used, never again.
|
|
151
|
-
*
|
|
152
|
-
* `off` is not "send nothing": opus-5 thinks by DEFAULT (measured — a bare request streams
|
|
153
|
-
* thinking_delta), so off has to say so with thinking.type=disabled.
|
|
154
|
-
*
|
|
155
|
-
* Extended thinking and tool use: the thinking blocks are NOT echoed back on the next turn, and
|
|
156
|
-
* measurement says they need not be — a tool_result turn that omits them answers 200 on both shapes
|
|
157
|
-
* and both model generations. */
|
|
158
|
-
// the dial itself (budgets, both Anthropic shapes, every OpenAI-compatible dialect) lives in thinking.ts;
|
|
159
|
-
// re-exported so the adapters' callers and tests keep one import
|
|
160
|
-
export { anthropicThinking, thinkingBudget, type AnthropicThinkingShape };
|
|
161
|
-
|
|
162
|
-
/** what the endpoint said when the shape was wrong — both spellings it uses. The message must ALSO name
|
|
163
|
-
* the dial (thinking/effort/budget): "not supported for this model" alone is how the API refuses other
|
|
164
|
-
* things too (a tool feature, an image), and flipping the shape on those would cost every later request
|
|
165
|
-
* a wasted round trip. */
|
|
166
|
-
const WRONG_SHAPE = /not supported for this model|does not support the effort parameter/i;
|
|
167
|
-
const ABOUT_THINKING = /thinking|effort|budget_tokens|output_config/i;
|
|
168
|
-
const wrongShape = (text: string): boolean => WRONG_SHAPE.test(text) && ABOUT_THINKING.test(text);
|
|
169
|
-
|
|
170
|
-
/** per provider+model, learned from a 400. Process-lifetime: a model's shape does not change under us.
|
|
171
|
-
* Keyed with the provider so a gateway that fronts the same model id differently cannot poison the
|
|
172
|
-
* direct endpoint's memory (or the other way round). Exported for tests only. */
|
|
173
|
-
export const shapeByModel = new Map<string, AnthropicThinkingShape>();
|
|
174
|
-
export const shapeKey = (model: ModelRef): string => `${model.provider}/${model.model}`;
|
|
175
|
-
|
|
176
|
-
/** the answer needs room BESIDE the thinking budget — never less than the caller asked for */
|
|
177
|
-
export function anthropicMaxTokens(model: ModelRef): number {
|
|
178
|
-
const base = model.maxTokens ?? 8192; // buildDef normally sets maxTokens from the catalog; 8192 is the floor every current Claude accepts
|
|
179
|
-
const budget = thinkingBudget(model.effort);
|
|
180
|
-
return budget === null ? base : Math.max(base, budget + 4096);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** the shape the next request for this model will use (for /effort and `model show` to say the truth) */
|
|
184
|
-
export function anthropicShapeFor(model: ModelRef): AnthropicThinkingShape { return shapeByModel.get(shapeKey(model)) ?? "effort"; }
|
|
185
|
-
|
|
186
|
-
/** POST to Anthropic, learning the model's thinking shape from a wrong-shape 400 and retrying ONCE.
|
|
187
|
-
* `build` is called per attempt because the shape changes the body. A failure that is NOT about the
|
|
188
|
-
* shape is returned as-is — the adapters turn it into an error turn (httpErrorTurn). Bounded: at most
|
|
189
|
-
* two sends per request, and the flipped shape is remembered only when the retry was not the same
|
|
190
|
-
* refusal — two wrong-shape 400s in a row (a model that takes neither) leave no memory, so the next
|
|
191
|
-
* request does not oscillate between two guaranteed failures. */
|
|
192
|
-
async function anthropicPost(url: string, headers: Record<string, string>, model: ModelRef, build: (extra: Record<string, unknown>) => unknown, signal: AbortSignal | undefined): Promise<Response> {
|
|
193
|
-
const wanted = model.effort;
|
|
194
|
-
const key = shapeKey(model);
|
|
195
|
-
let shape: AnthropicThinkingShape = shapeByModel.get(key) ?? "effort";
|
|
196
|
-
const send = (): Promise<Response> => fetchFirstByte(url, { method: "POST", headers, body: JSON.stringify(build(anthropicThinking(wanted, shape))), ...(signal ? { signal } : {}) });
|
|
197
|
-
const res = await send();
|
|
198
|
-
// nothing to learn when the request carried no level (auto/off/unset use one shape-free field), or when it worked
|
|
199
|
-
if (res.ok || wanted === undefined || wanted === "auto" || wanted === "off" || res.status !== 400) return res;
|
|
200
|
-
const text = await res.clone().text().catch(() => "");
|
|
201
|
-
if (!wrongShape(text)) return res;
|
|
202
|
-
shape = shape === "effort" ? "budget" : "effort";
|
|
203
|
-
const retry = await send();
|
|
204
|
-
if (retry.status === 400 && wrongShape(await retry.clone().text().catch(() => ""))) shapeByModel.delete(key);
|
|
205
|
-
else shapeByModel.set(key, shape);
|
|
206
|
-
return retry;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/** OpenAI-compatible endpoints name the dial in a dozen vocabularies (thinking.ts): the plan's fields are
|
|
210
|
-
* spread into the body after the profile's own wire fields (profiles.ts), so the dial wins a clash. */
|
|
211
|
-
function reasoningEffort(model: ModelRef): Record<string, unknown> {
|
|
212
|
-
return thinkingPlan(model, "openai").fields;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function openaiCompatStream(opts: AdapterOptions): StreamFn {
|
|
216
|
-
return async function* (model: ModelRef, messages: Message[], options?: { signal?: AbortSignal; tools?: unknown[] }): AsyncGenerator<StreamEvent> {
|
|
217
|
-
let turn: AssistantTurn;
|
|
218
|
-
try {
|
|
219
|
-
const res = await fetchFirstByte(opts.baseUrl.replace(/\/$/, "") + "/chat/completions", {
|
|
220
|
-
method: "POST",
|
|
221
|
-
headers: { ...(opts.headers ?? {}), "content-type": "application/json", authorization: `Bearer ${opts.apiKey}` },
|
|
222
|
-
body: JSON.stringify({
|
|
223
|
-
model: model.model,
|
|
224
|
-
messages: toOpenAiMessages(messages, wireOptions(model)),
|
|
225
|
-
...(options?.tools?.length ? { tools: toOpenAiToolSchemas(options.tools) } : {}),
|
|
226
|
-
stream: false,
|
|
227
|
-
...profileWire(model, false), // per-family endpoint fields (providers/profiles.ts); the effort word below wins a clash
|
|
228
|
-
...reasoningEffort(model),
|
|
229
|
-
...(model.maxTokens ? { max_tokens: model.maxTokens } : {}),
|
|
230
|
-
}),
|
|
231
|
-
signal: options?.signal,
|
|
232
|
-
});
|
|
233
|
-
if (!res.ok) {
|
|
234
|
-
turn = await httpErrorTurn(res); // status + Retry-After recorded for withRetry (stream-errors.ts)
|
|
235
|
-
} else {
|
|
236
|
-
turn = parseOpenAiResponse(await res.json());
|
|
237
|
-
}
|
|
238
|
-
} catch (e) {
|
|
239
|
-
turn = failedTurn(e, options?.signal);
|
|
240
|
-
}
|
|
241
|
-
yield { type: "turn", turn };
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** Streaming variant: emits text_delta events as they arrive, then the final turn. */
|
|
246
|
-
export function openaiCompatStreaming(opts: AdapterOptions): StreamFn {
|
|
247
|
-
return async function* (model: ModelRef, messages: Message[], options?: { signal?: AbortSignal; tools?: unknown[] }): AsyncGenerator<StreamEvent> {
|
|
248
|
-
let turn: AssistantTurn;
|
|
249
|
-
let buffer = "";
|
|
250
|
-
const toolArgs = new Map<number, { id: string; name: string; args: string }>();
|
|
251
|
-
try {
|
|
252
|
-
const res = await fetchFirstByte(opts.baseUrl.replace(/\/$/, "") + "/chat/completions", {
|
|
253
|
-
method: "POST",
|
|
254
|
-
headers: { ...(opts.headers ?? {}), ...SSE_HEADERS, "content-type": "application/json", authorization: `Bearer ${opts.apiKey}` },
|
|
255
|
-
body: JSON.stringify({
|
|
256
|
-
model: model.model,
|
|
257
|
-
messages: toOpenAiMessages(messages, wireOptions(model)),
|
|
258
|
-
...(options?.tools?.length ? { tools: toOpenAiToolSchemas(options.tools) } : {}),
|
|
259
|
-
stream: true,
|
|
260
|
-
// ask for the final usage chunk — without it most OpenAI-compat SSE streams omit usage
|
|
261
|
-
stream_options: { include_usage: true },
|
|
262
|
-
...profileWire(model, true), // per-family endpoint fields, streaming variant (tool_stream for GLM)
|
|
263
|
-
...reasoningEffort(model),
|
|
264
|
-
...(model.maxTokens ? { max_tokens: model.maxTokens } : {}),
|
|
265
|
-
}),
|
|
266
|
-
signal: options?.signal,
|
|
267
|
-
});
|
|
268
|
-
if (!res.ok || !res.body) {
|
|
269
|
-
turn = await httpErrorTurn(res); // status + Retry-After recorded for withRetry (stream-errors.ts)
|
|
270
|
-
yield { type: "turn", turn };
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (isJsonBody(res)) { yield { type: "turn", turn: parseOpenAiResponse(await res.json()) }; return; }
|
|
274
|
-
let finish: StopReason = "end_turn";
|
|
275
|
-
let usage: AssistantTurn["usage"] = { input: 0, output: 0 };
|
|
276
|
-
for await (const line of sseLines(res.body)) {
|
|
277
|
-
const ev = JSON.parse(line) as {
|
|
278
|
-
choices?: { delta?: { content?: string | null; reasoning_content?: string | null; tool_calls?: { index?: number; id?: string; function?: { name?: string; arguments?: string } }[] }; finish_reason?: string | null }[];
|
|
279
|
-
usage?: unknown;
|
|
280
|
-
};
|
|
281
|
-
const c = ev.choices?.[0];
|
|
282
|
-
if (c?.delta?.content) { buffer += c.delta.content; yield { type: "text_delta", text: c.delta.content }; }
|
|
283
|
-
// reasoning slices (GLM / DeepSeek-style `reasoning_content`): surfaced exactly like Anthropic's
|
|
284
|
-
// thinking_delta — the live status line counts them, the turn's parts never carry them
|
|
285
|
-
if (c?.delta?.reasoning_content) yield { type: "reasoning_delta", text: c.delta.reasoning_content };
|
|
286
|
-
for (const tc of c?.delta?.tool_calls ?? []) {
|
|
287
|
-
const idx = tc.index ?? 0;
|
|
288
|
-
const cur = toolArgs.get(idx) ?? { id: tc.id ?? `tc${idx}`, name: tc.function?.name ?? "", args: "" };
|
|
289
|
-
if (tc.id) cur.id = tc.id;
|
|
290
|
-
if (tc.function?.name) cur.name += tc.function.name;
|
|
291
|
-
if (tc.function?.arguments) cur.args += tc.function.arguments;
|
|
292
|
-
toolArgs.set(idx, cur);
|
|
293
|
-
}
|
|
294
|
-
if (c?.finish_reason) finish = c.finish_reason === "tool_calls" ? "tool_use" : c.finish_reason === "length" ? "length" : "end_turn";
|
|
295
|
-
if (ev.usage) {
|
|
296
|
-
// same normalization as the JSON adapters (parseOpenAiResponse/parseAnthropicResponse):
|
|
297
|
-
// cached_tokens subtracted from the inclusive prompt count, cacheRead/Write carried
|
|
298
|
-
const u = normalizeUsage(ev.usage);
|
|
299
|
-
usage = { input: u.input, output: u.output, cacheRead: u.cacheRead || undefined, cacheWrite: u.cacheWrite || undefined };
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const parts: AssistantTurn["parts"] = [];
|
|
303
|
-
if (buffer) parts.push({ kind: "text", text: buffer });
|
|
304
|
-
for (const [, tc] of [...toolArgs].sort((a, b) => a[0] - b[0])) {
|
|
305
|
-
let args: unknown = {};
|
|
306
|
-
try { args = JSON.parse(tc.args || "{}"); } catch { args = { _raw: tc.args }; }
|
|
307
|
-
parts.push({ kind: "tool_call", id: tc.id, tool: tc.name, args });
|
|
308
|
-
}
|
|
309
|
-
turn = { parts, stopReason: finish, usage };
|
|
310
|
-
} catch (e) {
|
|
311
|
-
turn = failedTurn(e, options?.signal, buffer); // mid-stream abort: the deltas already streamed survive as the turn's text
|
|
312
|
-
}
|
|
313
|
-
yield { type: "turn", turn };
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/** Streaming variant of the Anthropic adapter: emits text_delta as the model writes, then the final
|
|
318
|
-
* turn. The Messages SSE stream is a sequence of numbered content blocks — `content_block_start`
|
|
319
|
-
* opens one (text or tool_use), `content_block_delta` carries `text_delta` for prose and
|
|
320
|
-
* `input_json_delta` for a tool call's arguments (streamed as JSON text, one fragment at a time),
|
|
321
|
-
* `content_block_stop` closes it. Usage arrives in two halves: the input side on `message_start`,
|
|
322
|
-
* the output count on the final `message_delta` — they are merged so /cost sees the same shape the
|
|
323
|
-
* JSON adapter produces. Block ORDER is preserved: parts are emitted by block index, so a text
|
|
324
|
-
* block before a tool_use stays before it. */
|
|
325
|
-
export function anthropicStreaming(opts: AdapterOptions): StreamFn {
|
|
326
|
-
return async function* (model: ModelRef, messages: Message[], options?: { signal?: AbortSignal; tools?: unknown[] }): AsyncGenerator<StreamEvent> {
|
|
327
|
-
let turn: AssistantTurn;
|
|
328
|
-
let buffer = "";
|
|
329
|
-
/** open + finished blocks by wire index; `json` accumulates an input_json_delta */
|
|
330
|
-
const blocks = new Map<number, { kind: "text"; text: string } | { kind: "tool"; id: string; name: string; json: string } | { kind: "thinking" }>();
|
|
331
|
-
try {
|
|
332
|
-
const system = messages.filter((m) => m.role === "system").map((m) => partsText(m.parts)).join("\n");
|
|
333
|
-
const rest: Record<string, unknown> = {};
|
|
334
|
-
if (options?.tools?.length) rest.tools = options.tools.map((t) => { const sc = asToolSchema(t); return { name: sc.name, description: sc.description, input_schema: sc.args }; });
|
|
335
|
-
if (system) rest.system = system;
|
|
336
|
-
const body = (extra: Record<string, unknown>): Record<string, unknown> => ({
|
|
337
|
-
model: model.model,
|
|
338
|
-
max_tokens: anthropicMaxTokens(model),
|
|
339
|
-
messages: toAnthropicMessages(messages, wireOptions(model)),
|
|
340
|
-
stream: true,
|
|
341
|
-
...rest,
|
|
342
|
-
...extra,
|
|
343
|
-
});
|
|
344
|
-
const res = await anthropicPost(
|
|
345
|
-
opts.baseUrl.replace(/\/$/, "") + "/messages",
|
|
346
|
-
{ ...(opts.headers ?? {}), ...SSE_HEADERS, "content-type": "application/json", "x-api-key": opts.apiKey, "anthropic-version": "2023-06-01" },
|
|
347
|
-
model,
|
|
348
|
-
(extra) => applyAnthropicCacheBoundaries(body(extra)),
|
|
349
|
-
options?.signal,
|
|
350
|
-
);
|
|
351
|
-
if (!res.ok || !res.body) {
|
|
352
|
-
turn = await httpErrorTurn(res); // status + Retry-After recorded for withRetry (stream-errors.ts)
|
|
353
|
-
yield { type: "turn", turn };
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (isJsonBody(res)) { yield { type: "turn", turn: parseAnthropicResponse(await res.json()) }; return; }
|
|
357
|
-
let stop: StopReason = "end_turn";
|
|
358
|
-
let usage: AssistantTurn["usage"] = { input: 0, output: 0 };
|
|
359
|
-
for await (const line of sseLines(res.body)) {
|
|
360
|
-
const ev = JSON.parse(line) as {
|
|
361
|
-
type?: string;
|
|
362
|
-
index?: number;
|
|
363
|
-
message?: { usage?: unknown };
|
|
364
|
-
content_block?: { type?: string; id?: string; name?: string };
|
|
365
|
-
delta?: { type?: string; text?: string; partial_json?: string; thinking?: string; stop_reason?: string | null };
|
|
366
|
-
usage?: unknown;
|
|
367
|
-
error?: { type?: string; message?: string };
|
|
368
|
-
};
|
|
369
|
-
switch (ev.type) {
|
|
370
|
-
case "message_start": {
|
|
371
|
-
const u = normalizeUsage(ev.message?.usage);
|
|
372
|
-
usage = { input: u.input, output: u.output, cacheRead: u.cacheRead || undefined, cacheWrite: u.cacheWrite || undefined };
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
case "content_block_start": {
|
|
376
|
-
const i = ev.index ?? 0;
|
|
377
|
-
if (ev.content_block?.type === "tool_use") blocks.set(i, { kind: "tool", id: ev.content_block.id ?? `tc${i}`, name: ev.content_block.name ?? "unknown", json: "" });
|
|
378
|
-
else if (ev.content_block?.type === "thinking" || ev.content_block?.type === "redacted_thinking") blocks.set(i, { kind: "thinking" });
|
|
379
|
-
else blocks.set(i, { kind: "text", text: "" });
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
case "content_block_delta": {
|
|
383
|
-
const i = ev.index ?? 0;
|
|
384
|
-
const b = blocks.get(i);
|
|
385
|
-
if (ev.delta?.type === "text_delta" && ev.delta.text !== undefined) {
|
|
386
|
-
buffer += ev.delta.text;
|
|
387
|
-
if (b?.kind === "text") b.text += ev.delta.text;
|
|
388
|
-
else blocks.set(i, { kind: "text", text: ev.delta.text }); // a delta without its start
|
|
389
|
-
yield { type: "text_delta", text: ev.delta.text };
|
|
390
|
-
} else if (ev.delta?.type === "input_json_delta" && ev.delta.partial_json !== undefined && b?.kind === "tool") {
|
|
391
|
-
b.json += ev.delta.partial_json;
|
|
392
|
-
} else if (ev.delta?.type === "thinking_delta" && ev.delta.thinking !== undefined) {
|
|
393
|
-
// the model is reasoning: surface the slice so the TUI can prove work is happening, keep
|
|
394
|
-
// nothing — thinking is not the answer (signature_delta carries no tokens and is skipped)
|
|
395
|
-
yield { type: "reasoning_delta", text: ev.delta.thinking };
|
|
396
|
-
}
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
case "message_delta": {
|
|
400
|
-
if (ev.delta?.stop_reason === "max_tokens") stop = "length";
|
|
401
|
-
// the output count only exists here; the input side stays as message_start reported it
|
|
402
|
-
if (ev.usage !== undefined) {
|
|
403
|
-
const u = normalizeUsage(ev.usage);
|
|
404
|
-
usage = { ...usage, output: u.output || usage.output, cacheRead: usage.cacheRead ?? (u.cacheRead || undefined), cacheWrite: usage.cacheWrite ?? (u.cacheWrite || undefined) };
|
|
405
|
-
}
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
// a mid-stream `error` event ends the turn with what was already streamed
|
|
409
|
-
case "error":
|
|
410
|
-
throw new Error(ev.error?.message ?? "anthropic stream error");
|
|
411
|
-
default:
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const parts: AssistantTurn["parts"] = [];
|
|
416
|
-
for (const [, b] of [...blocks].sort((a, z) => a[0] - z[0])) {
|
|
417
|
-
if (b.kind === "thinking") continue; // reasoning never becomes a part
|
|
418
|
-
if (b.kind === "text") { if (b.text) parts.push({ kind: "text", text: b.text }); continue; }
|
|
419
|
-
let args: unknown = {};
|
|
420
|
-
try { args = JSON.parse(b.json || "{}"); } catch { args = { _raw: b.json }; }
|
|
421
|
-
parts.push({ kind: "tool_call", id: b.id, tool: b.name, args });
|
|
422
|
-
stop = "tool_use";
|
|
423
|
-
}
|
|
424
|
-
turn = { parts, stopReason: stop, usage };
|
|
425
|
-
} catch (e) {
|
|
426
|
-
turn = failedTurn(e, options?.signal, buffer); // mid-stream abort: the deltas already streamed survive as the turn's text
|
|
427
|
-
}
|
|
428
|
-
yield { type: "turn", turn };
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** Anthropic Messages protocol adapter. */
|
|
433
|
-
export function anthropicStream(opts: AdapterOptions): StreamFn {
|
|
434
|
-
return async function* (model: ModelRef, messages: Message[], options?: { signal?: AbortSignal; tools?: unknown[] }): AsyncGenerator<StreamEvent> {
|
|
435
|
-
let turn: AssistantTurn;
|
|
436
|
-
try {
|
|
437
|
-
const system = messages.filter((m) => m.role === "system").map((m) => partsText(m.parts)).join("\n");
|
|
438
|
-
const rest: Record<string, unknown> = {};
|
|
439
|
-
if (options?.tools?.length) rest.tools = options.tools.map((t) => { const sc = asToolSchema(t); return { name: sc.name, description: sc.description, input_schema: sc.args }; });
|
|
440
|
-
if (system) rest.system = system;
|
|
441
|
-
const body = (extra: Record<string, unknown>): Record<string, unknown> => ({
|
|
442
|
-
model: model.model,
|
|
443
|
-
max_tokens: anthropicMaxTokens(model),
|
|
444
|
-
messages: toAnthropicMessages(messages, wireOptions(model)),
|
|
445
|
-
...rest,
|
|
446
|
-
...extra,
|
|
447
|
-
});
|
|
448
|
-
// port #5: place prompt-cache breakpoints on the stable prefix (hermes pattern)
|
|
449
|
-
const res = await anthropicPost(
|
|
450
|
-
opts.baseUrl.replace(/\/$/, "") + "/messages",
|
|
451
|
-
{ ...(opts.headers ?? {}), "content-type": "application/json", "x-api-key": opts.apiKey, "anthropic-version": "2023-06-01" },
|
|
452
|
-
model,
|
|
453
|
-
(extra) => applyAnthropicCacheBoundaries(body(extra)),
|
|
454
|
-
options?.signal,
|
|
455
|
-
);
|
|
456
|
-
if (!res.ok) {
|
|
457
|
-
turn = await httpErrorTurn(res); // status + Retry-After recorded for withRetry (stream-errors.ts)
|
|
458
|
-
} else {
|
|
459
|
-
turn = parseAnthropicResponse(await res.json());
|
|
460
|
-
}
|
|
461
|
-
} catch (e) {
|
|
462
|
-
turn = failedTurn(e, options?.signal);
|
|
463
|
-
}
|
|
464
|
-
yield { type: "turn", turn };
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ---------- parsing ----------
|
|
469
|
-
|
|
470
|
-
function parseOpenAiResponse(json: unknown): AssistantTurn {
|
|
471
|
-
const j = json as {
|
|
472
|
-
choices: { message: { content: string | null; tool_calls?: { id: string; function: { name: string; arguments: string } }[] }; finish_reason: string | null }[];
|
|
473
|
-
usage?: { prompt_tokens: number; completion_tokens: number };
|
|
474
|
-
};
|
|
475
|
-
const c = j.choices?.[0];
|
|
476
|
-
const parts: AssistantTurn["parts"] = [];
|
|
477
|
-
if (c?.message.content) parts.push({ kind: "text", text: c.message.content });
|
|
478
|
-
for (const tc of c?.message.tool_calls ?? []) {
|
|
479
|
-
let args: unknown = {};
|
|
480
|
-
try { args = JSON.parse(tc.function.arguments || "{}"); } catch { args = { _raw: tc.function.arguments }; }
|
|
481
|
-
parts.push({ kind: "tool_call", id: tc.id, tool: tc.function.name, args });
|
|
482
|
-
}
|
|
483
|
-
const stop = (c?.message.tool_calls?.length ?? 0) > 0
|
|
484
|
-
? "tool_use"
|
|
485
|
-
: c?.finish_reason === "length" ? "length" : "end_turn";
|
|
486
|
-
const u = normalizeUsage(j.usage);
|
|
487
|
-
return { parts, stopReason: stop, usage: { input: u.input, output: u.output, cacheRead: u.cacheRead || undefined, cacheWrite: u.cacheWrite || undefined } };
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function parseAnthropicResponse(json: unknown): AssistantTurn {
|
|
491
|
-
const j = json as {
|
|
492
|
-
content: { type: string; text?: string; id?: string; name?: string; input?: unknown }[];
|
|
493
|
-
stop_reason: string | null;
|
|
494
|
-
usage: { input_tokens: number; output_tokens: number };
|
|
495
|
-
};
|
|
496
|
-
const parts: AssistantTurn["parts"] = [];
|
|
497
|
-
for (const b of j.content ?? []) {
|
|
498
|
-
if (b.type === "text" && b.text) parts.push({ kind: "text", text: b.text });
|
|
499
|
-
if (b.type === "tool_use" && b.id) parts.push({ kind: "tool_call", id: b.id, tool: b.name ?? "unknown", args: b.input ?? {} });
|
|
500
|
-
}
|
|
501
|
-
const stop = (j.content ?? []).some((b) => b.type === "tool_use") ? "tool_use" : j.stop_reason === "max_tokens" ? "length" : "end_turn";
|
|
502
|
-
const u = normalizeUsage(j.usage);
|
|
503
|
-
return { parts, stopReason: stop, usage: { input: u.input, output: u.output, cacheRead: u.cacheRead || undefined, cacheWrite: u.cacheWrite || undefined } };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// ---------- SSE: the line reader lives in sse.ts (shared with any future wire that must not import this file) ----------
|
|
507
|
-
|
|
508
|
-
// ---------- mock (test seam) ----------
|
|
509
|
-
|
|
510
|
-
export interface MockScript { turns: AssistantTurn[] }
|
|
511
|
-
|
|
512
|
-
export function mockStream(script: MockScript): StreamFn {
|
|
513
|
-
let i = 0;
|
|
514
|
-
return async function* (_model: ModelRef, _messages: Message[]): AsyncGenerator<StreamEvent> {
|
|
515
|
-
const turn = script.turns[Math.min(i, script.turns.length - 1)]!;
|
|
516
|
-
i++;
|
|
517
|
-
yield { type: "turn", turn };
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
export function textTurn(text: string): AssistantTurn {
|
|
522
|
-
return { parts: [{ kind: "text", text }], stopReason: "end_turn", usage: { input: 0, output: 1 } };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
export function toolTurn(calls: { id: string; tool: string; args: unknown }[]): AssistantTurn {
|
|
526
|
-
return { parts: calls.map((c) => ({ kind: "tool_call" as const, ...c })), stopReason: "tool_use", usage: { input: 0, output: 1 } };
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// ---------- registry: named providers from env/config ----------
|
|
530
|
-
// The provider table and the providers.json merge live in provider-config.ts (data) and the live
|
|
531
|
-
// dispatcher in registry.ts. resolveProvider() is the ONE-SHOT default lookup the CLI uses at boot.
|
|
532
|
-
|
|
533
|
-
/** Resolve the default provider config: ROVECODE_BASE_URL/ROVECODE_API_KEY override, else the
|
|
534
|
-
* providers.json `default` selector (when that provider has a key), else stored credential
|
|
535
|
-
* (`rovecode auth set`, port #37) in provider order, else a named env key, else a stored OAuth
|
|
536
|
-
* token (`rovecode auth login`, oauth/seam.ts — "a stored token is used when no API key is set"), else null.
|
|
537
|
-
*
|
|
538
|
-
* Precedence follows opencode provider.ts @ ebece6e: stored api keys are merged AFTER env
|
|
539
|
-
* (provider.ts:1578-1602, later mergeProvider patch wins) so a stored credential beats a
|
|
540
|
-
* named env key; the explicit pair keeps its documented "always wins" rank, like opencode's
|
|
541
|
-
* config source re-applied last (provider.ts:1643-1651). provider-config.ts pickDefault is
|
|
542
|
-
* the single implementation; the live registry shares it. */
|
|
543
|
-
export function resolveProvider(overrides: { baseUrl?: string; apiKey?: string; id?: string; protocol?: "openai" | "anthropic"; defaultModel?: string } = {}): ProviderConfig | null {
|
|
544
|
-
const base = overrides.baseUrl ?? process.env.ROVECODE_BASE_URL;
|
|
545
|
-
const key = overrides.apiKey ?? process.env.ROVECODE_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
546
|
-
if (base && key) {
|
|
547
|
-
const looksAnthropic = overrides.protocol === "anthropic" || base.includes("anthropic.com");
|
|
548
|
-
const defaultModel = overrides.defaultModel ?? process.env.ROVECODE_MODEL;
|
|
549
|
-
return { id: overrides.id ?? "custom", baseUrl: base, apiKey: key, protocol: looksAnthropic ? "anthropic" : "openai", ...(defaultModel !== undefined ? { defaultModel } : {}) };
|
|
550
|
-
}
|
|
551
|
-
const pick = pickDefault(buildSnapshot(process.cwd()));
|
|
552
|
-
if (pick === null) return resolveOAuthProviderConfig(); // a stored OAuth token is used when no API key is set; else null
|
|
553
|
-
const p = pick.provider;
|
|
554
|
-
return {
|
|
555
|
-
id: p.id, baseUrl: p.baseUrl, apiKey: p.apiKey ?? "", protocol: p.protocol,
|
|
556
|
-
...(pick.model !== undefined ? { defaultModel: pick.model } : {}),
|
|
557
|
-
...(p.headers !== undefined ? { headers: p.headers } : {}),
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
export function listBuiltinProviders(): { id: string; baseUrl: string; protocol: string; envKey: string; configured: boolean }[] {
|
|
562
|
-
return BUILTIN_PROVIDERS.map((p) => {
|
|
563
|
-
const envKey = p.keyEnv ?? `${p.id.toUpperCase()}_API_KEY`;
|
|
564
|
-
return { id: p.id, baseUrl: p.baseUrl, protocol: p.protocol, envKey, configured: Boolean(process.env[envKey]) };
|
|
565
|
-
});
|
|
566
|
-
}
|