gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.351157b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -134
- package/dist/cli.js +44 -6
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +123 -22
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +45 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +74 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +6 -12
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +156 -34
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +53 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +67 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +6 -14
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
|
@@ -4,50 +4,53 @@
|
|
|
4
4
|
* Keys are matched as prefixes against the model name (before the colon/tag).
|
|
5
5
|
* More specific entries should appear first.
|
|
6
6
|
*/
|
|
7
|
+
// Note: ollamaOptions.num_ctx is set for known model families where the context
|
|
8
|
+
// window is authoritative. For unknown/estimated models, num_ctx is NOT sent
|
|
9
|
+
// to avoid OOM risk — Ollama uses its own safe default instead.
|
|
7
10
|
const KNOWN_MODELS = [
|
|
8
11
|
// ─── Reasoning models ───────────────────────────────────────────────
|
|
9
|
-
["deepseek-r1", { contextWindow: 131072, reasoning: true }],
|
|
10
|
-
["qwq", { contextWindow: 131072, reasoning: true }],
|
|
12
|
+
["deepseek-r1", { contextWindow: 131072, reasoning: true, ollamaOptions: { num_ctx: 131072 } }],
|
|
13
|
+
["qwq", { contextWindow: 131072, reasoning: true, ollamaOptions: { num_ctx: 131072 } }],
|
|
11
14
|
// ─── Vision models ──────────────────────────────────────────────────
|
|
12
|
-
["llava", { contextWindow: 4096, input: ["text", "image"] }],
|
|
13
|
-
["bakllava", { contextWindow: 4096, input: ["text", "image"] }],
|
|
14
|
-
["moondream", { contextWindow: 8192, input: ["text", "image"] }],
|
|
15
|
-
["llama3.2-vision", { contextWindow: 131072, input: ["text", "image"] }],
|
|
16
|
-
["minicpm-v", { contextWindow: 4096, input: ["text", "image"] }],
|
|
15
|
+
["llava", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
|
|
16
|
+
["bakllava", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
|
|
17
|
+
["moondream", { contextWindow: 8192, input: ["text", "image"], ollamaOptions: { num_ctx: 8192 } }],
|
|
18
|
+
["llama3.2-vision", { contextWindow: 131072, input: ["text", "image"], ollamaOptions: { num_ctx: 131072 } }],
|
|
19
|
+
["minicpm-v", { contextWindow: 4096, input: ["text", "image"], ollamaOptions: { num_ctx: 4096 } }],
|
|
17
20
|
// ─── Code models ────────────────────────────────────────────────────
|
|
18
|
-
["codestral", { contextWindow: 262144, maxTokens: 32768 }],
|
|
19
|
-
["qwen2.5-coder", { contextWindow: 131072, maxTokens: 32768 }],
|
|
20
|
-
["deepseek-coder-v2", { contextWindow: 131072, maxTokens: 16384 }],
|
|
21
|
-
["starcoder2", { contextWindow: 16384, maxTokens: 8192 }],
|
|
22
|
-
["codegemma", { contextWindow: 8192, maxTokens: 8192 }],
|
|
23
|
-
["codellama", { contextWindow: 16384, maxTokens: 8192 }],
|
|
24
|
-
["devstral", { contextWindow: 131072, maxTokens: 32768 }],
|
|
21
|
+
["codestral", { contextWindow: 262144, maxTokens: 32768, ollamaOptions: { num_ctx: 262144 } }],
|
|
22
|
+
["qwen2.5-coder", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
|
|
23
|
+
["deepseek-coder-v2", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
24
|
+
["starcoder2", { contextWindow: 16384, maxTokens: 8192, ollamaOptions: { num_ctx: 16384 } }],
|
|
25
|
+
["codegemma", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
|
|
26
|
+
["codellama", { contextWindow: 16384, maxTokens: 8192, ollamaOptions: { num_ctx: 16384 } }],
|
|
27
|
+
["devstral", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
|
|
25
28
|
// ─── Llama family ───────────────────────────────────────────────────
|
|
26
|
-
["llama3.3", { contextWindow: 131072, maxTokens: 16384 }],
|
|
27
|
-
["llama3.2", { contextWindow: 131072, maxTokens: 16384 }],
|
|
28
|
-
["llama3.1", { contextWindow: 131072, maxTokens: 16384 }],
|
|
29
|
-
["llama3", { contextWindow: 8192, maxTokens: 8192 }],
|
|
30
|
-
["llama2", { contextWindow: 4096, maxTokens: 4096 }],
|
|
29
|
+
["llama3.3", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
30
|
+
["llama3.2", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
31
|
+
["llama3.1", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
32
|
+
["llama3", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
|
|
33
|
+
["llama2", { contextWindow: 4096, maxTokens: 4096, ollamaOptions: { num_ctx: 4096 } }],
|
|
31
34
|
// ─── Qwen family ────────────────────────────────────────────────────
|
|
32
|
-
["qwen3", { contextWindow: 131072, maxTokens: 32768 }],
|
|
33
|
-
["qwen2.5", { contextWindow: 131072, maxTokens: 32768 }],
|
|
34
|
-
["qwen2", { contextWindow: 131072, maxTokens: 32768 }],
|
|
35
|
+
["qwen3", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
|
|
36
|
+
["qwen2.5", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
|
|
37
|
+
["qwen2", { contextWindow: 131072, maxTokens: 32768, ollamaOptions: { num_ctx: 131072 } }],
|
|
35
38
|
// ─── Gemma family ───────────────────────────────────────────────────
|
|
36
|
-
["gemma3", { contextWindow: 131072, maxTokens: 16384 }],
|
|
37
|
-
["gemma2", { contextWindow: 8192, maxTokens: 8192 }],
|
|
39
|
+
["gemma3", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
40
|
+
["gemma2", { contextWindow: 8192, maxTokens: 8192, ollamaOptions: { num_ctx: 8192 } }],
|
|
38
41
|
// ─── Mistral family ─────────────────────────────────────────────────
|
|
39
|
-
["mistral-large", { contextWindow: 131072, maxTokens: 16384 }],
|
|
40
|
-
["mistral-small", { contextWindow: 131072, maxTokens: 16384 }],
|
|
41
|
-
["mistral-nemo", { contextWindow: 131072, maxTokens: 16384 }],
|
|
42
|
-
["mistral", { contextWindow: 32768, maxTokens: 8192 }],
|
|
43
|
-
["mixtral", { contextWindow: 32768, maxTokens: 8192 }],
|
|
42
|
+
["mistral-large", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
43
|
+
["mistral-small", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
44
|
+
["mistral-nemo", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
45
|
+
["mistral", { contextWindow: 32768, maxTokens: 8192, ollamaOptions: { num_ctx: 32768 } }],
|
|
46
|
+
["mixtral", { contextWindow: 32768, maxTokens: 8192, ollamaOptions: { num_ctx: 32768 } }],
|
|
44
47
|
// ─── Phi family ─────────────────────────────────────────────────────
|
|
45
|
-
["phi4", { contextWindow: 16384, maxTokens: 16384 }],
|
|
46
|
-
["phi3.5", { contextWindow: 131072, maxTokens: 16384 }],
|
|
47
|
-
["phi3", { contextWindow: 131072, maxTokens: 4096 }],
|
|
48
|
+
["phi4", { contextWindow: 16384, maxTokens: 16384, ollamaOptions: { num_ctx: 16384 } }],
|
|
49
|
+
["phi3.5", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
50
|
+
["phi3", { contextWindow: 131072, maxTokens: 4096, ollamaOptions: { num_ctx: 131072 } }],
|
|
48
51
|
// ─── Command R ──────────────────────────────────────────────────────
|
|
49
|
-
["command-r-plus", { contextWindow: 131072, maxTokens: 16384 }],
|
|
50
|
-
["command-r", { contextWindow: 131072, maxTokens: 16384 }],
|
|
52
|
+
["command-r-plus", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
53
|
+
["command-r", { contextWindow: 131072, maxTokens: 16384, ollamaOptions: { num_ctx: 131072 } }],
|
|
51
54
|
];
|
|
52
55
|
/**
|
|
53
56
|
* Look up capabilities for a model by name.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// GSD2 — Ollama Extension: NDJSON streaming parser
|
|
2
|
+
/**
|
|
3
|
+
* Parses a streaming NDJSON (newline-delimited JSON) response body into
|
|
4
|
+
* typed objects. Used for Ollama's /api/chat and /api/pull endpoints.
|
|
5
|
+
*
|
|
6
|
+
* @param strict When true, malformed JSON lines throw instead of being skipped.
|
|
7
|
+
* Use strict mode for inference streams where silent data loss is unacceptable.
|
|
8
|
+
* Use permissive mode (default) for progress endpoints like /api/pull.
|
|
9
|
+
*/
|
|
10
|
+
export async function* parseNDJsonStream(body, signal, strict = false) {
|
|
11
|
+
const reader = body.getReader();
|
|
12
|
+
const decoder = new TextDecoder();
|
|
13
|
+
let buffer = "";
|
|
14
|
+
try {
|
|
15
|
+
while (true) {
|
|
16
|
+
if (signal?.aborted)
|
|
17
|
+
break;
|
|
18
|
+
const { done, value } = await reader.read();
|
|
19
|
+
if (done)
|
|
20
|
+
break;
|
|
21
|
+
buffer += decoder.decode(value, { stream: true });
|
|
22
|
+
const lines = buffer.split("\n");
|
|
23
|
+
buffer = lines.pop() ?? "";
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed)
|
|
27
|
+
continue;
|
|
28
|
+
try {
|
|
29
|
+
yield JSON.parse(trimmed);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (strict) {
|
|
33
|
+
throw new Error(`Malformed NDJSON line from Ollama: ${trimmed.slice(0, 200)}`);
|
|
34
|
+
}
|
|
35
|
+
// Permissive mode: skip malformed lines
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Flush remaining buffer (skip if aborted)
|
|
40
|
+
if (buffer.trim() && !signal?.aborted) {
|
|
41
|
+
try {
|
|
42
|
+
yield JSON.parse(buffer.trim());
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (strict) {
|
|
46
|
+
throw new Error(`Malformed NDJSON line from Ollama: ${buffer.trim().slice(0, 200)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
reader.releaseLock();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// GSD2 — Ollama Extension: Native /api/chat stream provider
|
|
2
|
+
/**
|
|
3
|
+
* Implements the "ollama-chat" API provider, streaming responses directly
|
|
4
|
+
* from Ollama's native /api/chat endpoint instead of the OpenAI compatibility
|
|
5
|
+
* shim. This exposes Ollama-specific options (num_ctx, keep_alive, num_gpu,
|
|
6
|
+
* sampling parameters) and surfaces inference performance metrics.
|
|
7
|
+
*/
|
|
8
|
+
import { EventStream, } from "@gsd/pi-ai";
|
|
9
|
+
import { chat } from "./ollama-client.js";
|
|
10
|
+
import { ThinkingTagParser } from "./thinking-parser.js";
|
|
11
|
+
/** Create an AssistantMessageEventStream using the base EventStream class. */
|
|
12
|
+
function createStream() {
|
|
13
|
+
return new EventStream((event) => event.type === "done" || event.type === "error", (event) => {
|
|
14
|
+
if (event.type === "done")
|
|
15
|
+
return event.message;
|
|
16
|
+
if (event.type === "error")
|
|
17
|
+
return event.error;
|
|
18
|
+
throw new Error("Unexpected event type for final result");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// ─── Stream handler ─────────────────────────────────────────────────────────
|
|
22
|
+
export function streamOllamaChat(model, context, options) {
|
|
23
|
+
const stream = createStream();
|
|
24
|
+
(async () => {
|
|
25
|
+
const output = buildInitialOutput(model);
|
|
26
|
+
try {
|
|
27
|
+
const request = buildRequest(model, context, options);
|
|
28
|
+
stream.push({ type: "start", partial: output });
|
|
29
|
+
const useThinkingParser = model.reasoning;
|
|
30
|
+
const thinkParser = useThinkingParser ? new ThinkingTagParser() : null;
|
|
31
|
+
let contentIndex = -1;
|
|
32
|
+
let currentBlockType = null;
|
|
33
|
+
function startBlock(type) {
|
|
34
|
+
contentIndex++;
|
|
35
|
+
currentBlockType = type;
|
|
36
|
+
if (type === "text") {
|
|
37
|
+
output.content.push({ type: "text", text: "" });
|
|
38
|
+
stream.push({ type: "text_start", contentIndex, partial: output });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
output.content.push({ type: "thinking", thinking: "" });
|
|
42
|
+
stream.push({ type: "thinking_start", contentIndex, partial: output });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function endBlock() {
|
|
46
|
+
if (currentBlockType === null)
|
|
47
|
+
return;
|
|
48
|
+
if (currentBlockType === "text") {
|
|
49
|
+
const block = output.content[contentIndex];
|
|
50
|
+
stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const block = output.content[contentIndex];
|
|
54
|
+
stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: output });
|
|
55
|
+
}
|
|
56
|
+
currentBlockType = null;
|
|
57
|
+
}
|
|
58
|
+
function emitDelta(type, text) {
|
|
59
|
+
if (!text)
|
|
60
|
+
return;
|
|
61
|
+
if (currentBlockType !== type) {
|
|
62
|
+
endBlock();
|
|
63
|
+
startBlock(type);
|
|
64
|
+
}
|
|
65
|
+
if (type === "text") {
|
|
66
|
+
output.content[contentIndex].text += text;
|
|
67
|
+
stream.push({ type: "text_delta", contentIndex, delta: text, partial: output });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
output.content[contentIndex].thinking += text;
|
|
71
|
+
stream.push({ type: "thinking_delta", contentIndex, delta: text, partial: output });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function processChunks(chunks) {
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
emitDelta(chunk.type, chunk.text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function processToolCalls(toolCalls) {
|
|
80
|
+
endBlock();
|
|
81
|
+
for (const tc of toolCalls) {
|
|
82
|
+
contentIndex++;
|
|
83
|
+
const toolCall = {
|
|
84
|
+
type: "toolCall",
|
|
85
|
+
id: `ollama_tc_${contentIndex}`,
|
|
86
|
+
name: tc.function.name,
|
|
87
|
+
arguments: tc.function.arguments,
|
|
88
|
+
};
|
|
89
|
+
output.content.push(toolCall);
|
|
90
|
+
stream.push({ type: "toolcall_start", contentIndex, partial: output });
|
|
91
|
+
// Emit a delta with the serialized arguments (convention: start/delta/end)
|
|
92
|
+
stream.push({
|
|
93
|
+
type: "toolcall_delta",
|
|
94
|
+
contentIndex,
|
|
95
|
+
delta: JSON.stringify(tc.function.arguments),
|
|
96
|
+
partial: output,
|
|
97
|
+
});
|
|
98
|
+
stream.push({
|
|
99
|
+
type: "toolcall_end",
|
|
100
|
+
contentIndex,
|
|
101
|
+
toolCall,
|
|
102
|
+
partial: output,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
output.stopReason = "toolUse";
|
|
106
|
+
}
|
|
107
|
+
for await (const chunk of chat(request, options?.signal)) {
|
|
108
|
+
// Handle text content — process independently of tool_calls
|
|
109
|
+
// (a chunk may contain both content and tool_calls)
|
|
110
|
+
const content = chunk.message?.content ?? "";
|
|
111
|
+
if (content && !chunk.done) {
|
|
112
|
+
if (thinkParser) {
|
|
113
|
+
processChunks(thinkParser.push(content));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
emitDelta("text", content);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Handle tool calls (Ollama sends them complete, may be on done:true chunk)
|
|
120
|
+
if (chunk.message?.tool_calls?.length) {
|
|
121
|
+
processToolCalls(chunk.message.tool_calls);
|
|
122
|
+
}
|
|
123
|
+
if (chunk.done) {
|
|
124
|
+
// Final chunk — extract metrics and usage
|
|
125
|
+
if (thinkParser)
|
|
126
|
+
processChunks(thinkParser.flush());
|
|
127
|
+
endBlock();
|
|
128
|
+
output.usage = buildUsage(chunk);
|
|
129
|
+
output.inferenceMetrics = extractMetrics(chunk);
|
|
130
|
+
// Preserve "toolUse" if tool calls were processed
|
|
131
|
+
if (output.stopReason !== "toolUse") {
|
|
132
|
+
output.stopReason = mapStopReason(chunk.done_reason);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
assertStreamSuccess(output, options?.signal);
|
|
138
|
+
finalizeStream(stream, output);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
handleStreamError(stream, output, error, options?.signal);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
return stream;
|
|
145
|
+
}
|
|
146
|
+
// ─── Request building ───────────────────────────────────────────────────────
|
|
147
|
+
function buildRequest(model, context, options) {
|
|
148
|
+
const ollamaOpts = (model.providerOptions ?? {});
|
|
149
|
+
const request = {
|
|
150
|
+
model: model.id,
|
|
151
|
+
messages: convertMessages(context),
|
|
152
|
+
stream: true,
|
|
153
|
+
};
|
|
154
|
+
// Build options block with all Ollama-specific parameters
|
|
155
|
+
const reqOptions = {};
|
|
156
|
+
// Context window — only sent when explicitly configured via providerOptions.
|
|
157
|
+
// Sending inferred/estimated values risks OOM on constrained hosts.
|
|
158
|
+
// Users can set num_ctx per-model in models.json ollamaOptions or the
|
|
159
|
+
// capability table can provide it for known model families.
|
|
160
|
+
if (ollamaOpts.num_ctx !== undefined && ollamaOpts.num_ctx > 0) {
|
|
161
|
+
reqOptions.num_ctx = ollamaOpts.num_ctx;
|
|
162
|
+
}
|
|
163
|
+
// Max output tokens
|
|
164
|
+
const maxTokens = options?.maxTokens ?? model.maxTokens;
|
|
165
|
+
if (maxTokens > 0) {
|
|
166
|
+
reqOptions.num_predict = maxTokens;
|
|
167
|
+
}
|
|
168
|
+
// Temperature
|
|
169
|
+
if (options?.temperature !== undefined) {
|
|
170
|
+
reqOptions.temperature = options.temperature;
|
|
171
|
+
}
|
|
172
|
+
// Per-model sampling options from providerOptions
|
|
173
|
+
if (ollamaOpts.top_p !== undefined)
|
|
174
|
+
reqOptions.top_p = ollamaOpts.top_p;
|
|
175
|
+
if (ollamaOpts.top_k !== undefined)
|
|
176
|
+
reqOptions.top_k = ollamaOpts.top_k;
|
|
177
|
+
if (ollamaOpts.repeat_penalty !== undefined)
|
|
178
|
+
reqOptions.repeat_penalty = ollamaOpts.repeat_penalty;
|
|
179
|
+
if (ollamaOpts.seed !== undefined)
|
|
180
|
+
reqOptions.seed = ollamaOpts.seed;
|
|
181
|
+
if (ollamaOpts.num_gpu !== undefined)
|
|
182
|
+
reqOptions.num_gpu = ollamaOpts.num_gpu;
|
|
183
|
+
if (Object.keys(reqOptions).length > 0) {
|
|
184
|
+
request.options = reqOptions;
|
|
185
|
+
}
|
|
186
|
+
// Keep alive
|
|
187
|
+
if (ollamaOpts.keep_alive !== undefined) {
|
|
188
|
+
request.keep_alive = ollamaOpts.keep_alive;
|
|
189
|
+
}
|
|
190
|
+
// Tools
|
|
191
|
+
if (context.tools?.length) {
|
|
192
|
+
request.tools = convertTools(context.tools);
|
|
193
|
+
}
|
|
194
|
+
return request;
|
|
195
|
+
}
|
|
196
|
+
// ─── Message conversion ─────────────────────────────────────────────────────
|
|
197
|
+
function convertMessages(context) {
|
|
198
|
+
const messages = [];
|
|
199
|
+
// System prompt
|
|
200
|
+
if (context.systemPrompt) {
|
|
201
|
+
messages.push({ role: "system", content: context.systemPrompt });
|
|
202
|
+
}
|
|
203
|
+
for (const msg of context.messages) {
|
|
204
|
+
switch (msg.role) {
|
|
205
|
+
case "user":
|
|
206
|
+
messages.push(convertUserMessage(msg));
|
|
207
|
+
break;
|
|
208
|
+
case "assistant":
|
|
209
|
+
messages.push(convertAssistantMessage(msg));
|
|
210
|
+
break;
|
|
211
|
+
case "toolResult":
|
|
212
|
+
messages.push({
|
|
213
|
+
role: "tool",
|
|
214
|
+
content: msg.content
|
|
215
|
+
.filter((c) => c.type === "text")
|
|
216
|
+
.map((c) => c.text)
|
|
217
|
+
.join("\n"),
|
|
218
|
+
name: msg.toolName,
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return messages;
|
|
224
|
+
}
|
|
225
|
+
function convertUserMessage(msg) {
|
|
226
|
+
if (typeof msg.content === "string") {
|
|
227
|
+
return { role: "user", content: msg.content };
|
|
228
|
+
}
|
|
229
|
+
const textParts = [];
|
|
230
|
+
const images = [];
|
|
231
|
+
for (const part of msg.content) {
|
|
232
|
+
if (part.type === "text") {
|
|
233
|
+
textParts.push(part.text);
|
|
234
|
+
}
|
|
235
|
+
else if (part.type === "image") {
|
|
236
|
+
// Strip data URI prefix if present
|
|
237
|
+
let data = part.data;
|
|
238
|
+
const commaIdx = data.indexOf(",");
|
|
239
|
+
if (commaIdx !== -1 && data.startsWith("data:")) {
|
|
240
|
+
data = data.slice(commaIdx + 1);
|
|
241
|
+
}
|
|
242
|
+
images.push(data);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const result = {
|
|
246
|
+
role: "user",
|
|
247
|
+
content: textParts.join("\n"),
|
|
248
|
+
};
|
|
249
|
+
if (images.length > 0) {
|
|
250
|
+
result.images = images;
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
function convertAssistantMessage(msg) {
|
|
255
|
+
let content = "";
|
|
256
|
+
const toolCalls = [];
|
|
257
|
+
for (const block of msg.content) {
|
|
258
|
+
if (block.type === "thinking") {
|
|
259
|
+
// Serialize thinking back inline for round-trip with Ollama
|
|
260
|
+
content += `<think>${block.thinking}</think>`;
|
|
261
|
+
}
|
|
262
|
+
else if (block.type === "text") {
|
|
263
|
+
content += block.text;
|
|
264
|
+
}
|
|
265
|
+
else if (block.type === "toolCall") {
|
|
266
|
+
const tc = block;
|
|
267
|
+
toolCalls.push({
|
|
268
|
+
function: {
|
|
269
|
+
name: tc.name,
|
|
270
|
+
arguments: tc.arguments,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const result = { role: "assistant", content };
|
|
276
|
+
if (toolCalls.length > 0) {
|
|
277
|
+
result.tool_calls = toolCalls;
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
// ─── Tool conversion ────────────────────────────────────────────────────────
|
|
282
|
+
function convertTools(tools) {
|
|
283
|
+
return tools.map((tool) => {
|
|
284
|
+
const params = tool.parameters;
|
|
285
|
+
return {
|
|
286
|
+
type: "function",
|
|
287
|
+
function: {
|
|
288
|
+
name: tool.name,
|
|
289
|
+
description: tool.description,
|
|
290
|
+
parameters: {
|
|
291
|
+
type: "object",
|
|
292
|
+
required: params.required,
|
|
293
|
+
properties: params.properties ?? {},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// ─── Response mapping ───────────────────────────────────────────────────────
|
|
300
|
+
function mapStopReason(doneReason) {
|
|
301
|
+
switch (doneReason) {
|
|
302
|
+
case "stop":
|
|
303
|
+
return "stop";
|
|
304
|
+
case "length":
|
|
305
|
+
return "length";
|
|
306
|
+
default:
|
|
307
|
+
return "stop";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function buildUsage(chunk) {
|
|
311
|
+
const input = chunk.prompt_eval_count ?? 0;
|
|
312
|
+
const outputTokens = chunk.eval_count ?? 0;
|
|
313
|
+
return {
|
|
314
|
+
input,
|
|
315
|
+
output: outputTokens,
|
|
316
|
+
cacheRead: 0,
|
|
317
|
+
cacheWrite: 0,
|
|
318
|
+
totalTokens: input + outputTokens,
|
|
319
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function extractMetrics(chunk) {
|
|
323
|
+
if (!chunk.eval_duration && !chunk.total_duration)
|
|
324
|
+
return undefined;
|
|
325
|
+
const evalCount = chunk.eval_count ?? 0;
|
|
326
|
+
const evalDurationNs = chunk.eval_duration ?? 0;
|
|
327
|
+
const evalDurationMs = evalDurationNs / 1e6;
|
|
328
|
+
const tokensPerSecond = evalDurationNs > 0 ? evalCount / (evalDurationNs / 1e9) : 0;
|
|
329
|
+
return {
|
|
330
|
+
tokensPerSecond,
|
|
331
|
+
totalDurationMs: (chunk.total_duration ?? 0) / 1e6,
|
|
332
|
+
evalDurationMs,
|
|
333
|
+
promptEvalDurationMs: (chunk.prompt_eval_duration ?? 0) / 1e6,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// ─── Stream lifecycle helpers ───────────────────────────────────────────────
|
|
337
|
+
// Replicated from openai-shared.ts (not exported from @gsd/pi-ai)
|
|
338
|
+
function buildInitialOutput(model) {
|
|
339
|
+
return {
|
|
340
|
+
role: "assistant",
|
|
341
|
+
content: [],
|
|
342
|
+
api: model.api,
|
|
343
|
+
provider: model.provider,
|
|
344
|
+
model: model.id,
|
|
345
|
+
usage: {
|
|
346
|
+
input: 0,
|
|
347
|
+
output: 0,
|
|
348
|
+
cacheRead: 0,
|
|
349
|
+
cacheWrite: 0,
|
|
350
|
+
totalTokens: 0,
|
|
351
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
352
|
+
},
|
|
353
|
+
stopReason: "stop",
|
|
354
|
+
timestamp: Date.now(),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function assertStreamSuccess(output, signal) {
|
|
358
|
+
if (signal?.aborted) {
|
|
359
|
+
throw new Error("Request was aborted");
|
|
360
|
+
}
|
|
361
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
362
|
+
throw new Error("An unknown error occurred");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function finalizeStream(stream, output) {
|
|
366
|
+
stream.push({
|
|
367
|
+
type: "done",
|
|
368
|
+
reason: output.stopReason,
|
|
369
|
+
message: output,
|
|
370
|
+
});
|
|
371
|
+
stream.end();
|
|
372
|
+
}
|
|
373
|
+
function handleStreamError(stream, output, error, signal) {
|
|
374
|
+
for (const block of output.content)
|
|
375
|
+
delete block.index;
|
|
376
|
+
output.stopReason = signal?.aborted ? "aborted" : "error";
|
|
377
|
+
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
378
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
379
|
+
stream.end();
|
|
380
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// GSD2 — HTTP client for Ollama REST API
|
|
2
|
+
import { parseNDJsonStream } from "./ndjson-stream.js";
|
|
2
3
|
const DEFAULT_HOST = "http://localhost:11434";
|
|
3
4
|
const PROBE_TIMEOUT_MS = 1500;
|
|
4
5
|
const REQUEST_TIMEOUT_MS = 10000;
|
|
@@ -104,40 +105,30 @@ export async function pullModel(name, onProgress, signal) {
|
|
|
104
105
|
if (!response.body) {
|
|
105
106
|
throw new Error("Ollama /api/pull returned no body");
|
|
106
107
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
let buffer = "";
|
|
110
|
-
while (true) {
|
|
111
|
-
const { done, value } = await reader.read();
|
|
112
|
-
if (done)
|
|
113
|
-
break;
|
|
114
|
-
buffer += decoder.decode(value, { stream: true });
|
|
115
|
-
const lines = buffer.split("\n");
|
|
116
|
-
buffer = lines.pop() ?? "";
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
const trimmed = line.trim();
|
|
119
|
-
if (!trimmed)
|
|
120
|
-
continue;
|
|
121
|
-
try {
|
|
122
|
-
const progress = JSON.parse(trimmed);
|
|
123
|
-
onProgress?.(progress);
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// Skip malformed lines
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// Process remaining buffer
|
|
131
|
-
if (buffer.trim()) {
|
|
132
|
-
try {
|
|
133
|
-
const progress = JSON.parse(buffer.trim());
|
|
134
|
-
onProgress?.(progress);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
// Ignore
|
|
138
|
-
}
|
|
108
|
+
for await (const progress of parseNDJsonStream(response.body, signal)) {
|
|
109
|
+
onProgress?.(progress);
|
|
139
110
|
}
|
|
140
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Stream a chat completion via /api/chat.
|
|
114
|
+
* Returns an async generator yielding each NDJSON response chunk.
|
|
115
|
+
*/
|
|
116
|
+
export async function* chat(request, signal) {
|
|
117
|
+
const response = await fetch(`${getOllamaHost()}/api/chat`, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: { "Content-Type": "application/json" },
|
|
120
|
+
body: JSON.stringify(request),
|
|
121
|
+
signal,
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
throw new Error(`Ollama /api/chat returned ${response.status}: ${text}`);
|
|
126
|
+
}
|
|
127
|
+
if (!response.body) {
|
|
128
|
+
throw new Error("Ollama /api/chat returned no body");
|
|
129
|
+
}
|
|
130
|
+
yield* parseNDJsonStream(response.body, signal, true);
|
|
131
|
+
}
|
|
141
132
|
/**
|
|
142
133
|
* Delete a local model.
|
|
143
134
|
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Returns models in the format expected by pi.registerProvider().
|
|
8
8
|
*/
|
|
9
|
-
import { listModels
|
|
9
|
+
import { listModels } from "./ollama-client.js";
|
|
10
10
|
import { estimateContextFromParams, formatModelSize, getModelCapabilities, humanizeModelName, } from "./model-capabilities.js";
|
|
11
11
|
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
12
12
|
function enrichModel(info) {
|
|
@@ -32,6 +32,7 @@ function enrichModel(info) {
|
|
|
32
32
|
maxTokens,
|
|
33
33
|
sizeBytes: info.size,
|
|
34
34
|
parameterSize,
|
|
35
|
+
ollamaOptions: caps.ollamaOptions,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
@@ -61,9 +62,3 @@ export function formatModelForDisplay(model) {
|
|
|
61
62
|
}
|
|
62
63
|
return parts.join(" ");
|
|
63
64
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Build the OpenAI-compat base URL for Ollama.
|
|
66
|
-
*/
|
|
67
|
-
export function getOllamaOpenAIBaseUrl() {
|
|
68
|
-
return `${getOllamaHost()}/v1`;
|
|
69
|
-
}
|