gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff
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 +48 -6
- package/dist/headless-query.js +11 -1
- 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/detect-stuck.js +27 -0
- 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 +157 -22
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +124 -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-timers.js +2 -1
- 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 +147 -75
- 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 +32 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
- 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 +2 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- 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 +4 -7
- 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/safety/content-validator.js +73 -0
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
- package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
- 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 +28 -22
- 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/update-cmd.js +4 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- 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 +20 -20
- 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/provider-registration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
- 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 +12 -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 +9 -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/provider-registration.test.ts +81 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +14 -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 +10 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
- 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 +194 -33
- package/src/resources/extensions/gsd/auto/session.ts +14 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +141 -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-timers.ts +2 -1
- 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 +160 -88
- 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 +36 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
- 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 +16 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- 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 +4 -7
- 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/safety/content-validator.ts +98 -0
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
- package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
- 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-slice-string-coercion.test.ts +211 -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/flat-rate-routing-guard.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -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/stuck-detection-coverage.test.ts +42 -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-logger.ts +2 -1
- 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 +26 -25
- 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-auth-mode.test.ts +20 -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-chat-provider-stream.test.ts +82 -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 → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
|
@@ -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) {
|
|
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
|
-
}
|
|
@@ -19,6 +19,8 @@ export function registerOllamaTool(pi) {
|
|
|
19
19
|
promptGuidelines: [
|
|
20
20
|
"Use 'list' to see what models are available locally before trying to use one.",
|
|
21
21
|
"Use 'pull' to download a model that isn't available yet.",
|
|
22
|
+
"Use 'remove' to delete a local model that is no longer needed.",
|
|
23
|
+
"Use 'show' to get detailed info about a model (parameters, quantization, families).",
|
|
22
24
|
"Use 'status' to check if Ollama is running.",
|
|
23
25
|
"Use 'ps' to see which models are loaded in memory and VRAM usage.",
|
|
24
26
|
"Common models: llama3.1:8b, qwen2.5-coder:7b, deepseek-r1:8b, codestral:22b",
|
|
@@ -27,6 +29,8 @@ export function registerOllamaTool(pi) {
|
|
|
27
29
|
action: Type.Union([
|
|
28
30
|
Type.Literal("list"),
|
|
29
31
|
Type.Literal("pull"),
|
|
32
|
+
Type.Literal("remove"),
|
|
33
|
+
Type.Literal("show"),
|
|
30
34
|
Type.Literal("status"),
|
|
31
35
|
Type.Literal("ps"),
|
|
32
36
|
], { description: "Action to perform" }),
|
|
@@ -134,6 +138,64 @@ export function registerOllamaTool(pi) {
|
|
|
134
138
|
details: { action, modelCount: ps.models.length, durationMs: Date.now() - startTime },
|
|
135
139
|
};
|
|
136
140
|
}
|
|
141
|
+
case "remove": {
|
|
142
|
+
if (!model) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: "Error: 'model' parameter is required for remove action." }],
|
|
145
|
+
isError: true,
|
|
146
|
+
details: { action, durationMs: Date.now() - startTime, error: "missing_model" },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const running = await client.isRunning();
|
|
150
|
+
if (!running) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: "Ollama is not running." }],
|
|
153
|
+
isError: true,
|
|
154
|
+
details: { action, model, durationMs: Date.now() - startTime, error: "not_running" },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
await client.deleteModel(model);
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: "text", text: `Successfully removed ${model}` }],
|
|
160
|
+
details: { action, model, durationMs: Date.now() - startTime },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case "show": {
|
|
164
|
+
if (!model) {
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: "Error: 'model' parameter is required for show action." }],
|
|
167
|
+
isError: true,
|
|
168
|
+
details: { action, durationMs: Date.now() - startTime, error: "missing_model" },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const running = await client.isRunning();
|
|
172
|
+
if (!running) {
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: "text", text: "Ollama is not running." }],
|
|
175
|
+
isError: true,
|
|
176
|
+
details: { action, model, durationMs: Date.now() - startTime, error: "not_running" },
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const info = await client.showModel(model);
|
|
180
|
+
const details = info.details;
|
|
181
|
+
const infoLines = [
|
|
182
|
+
`Model: ${model}`,
|
|
183
|
+
`Family: ${details.family}`,
|
|
184
|
+
`Parameters: ${details.parameter_size}`,
|
|
185
|
+
`Quantization: ${details.quantization_level}`,
|
|
186
|
+
`Format: ${details.format}`,
|
|
187
|
+
];
|
|
188
|
+
if (details.families?.length) {
|
|
189
|
+
infoLines.push(`Families: ${details.families.join(", ")}`);
|
|
190
|
+
}
|
|
191
|
+
if (info.parameters) {
|
|
192
|
+
infoLines.push(`\nModelfile parameters:\n${info.parameters}`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: infoLines.join("\n") }],
|
|
196
|
+
details: { action, model, durationMs: Date.now() - startTime },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
137
199
|
default:
|
|
138
200
|
return {
|
|
139
201
|
content: [{ type: "text", text: `Unknown action: ${action}` }],
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// GSD2 — Ollama Extension: Stateful <think> tag stream parser
|
|
2
|
+
const OPEN_TAG = "<think>";
|
|
3
|
+
const CLOSE_TAG = "</think>";
|
|
4
|
+
const MAX_TAG_LEN = Math.max(OPEN_TAG.length, CLOSE_TAG.length);
|
|
5
|
+
export class ThinkingTagParser {
|
|
6
|
+
buffer = "";
|
|
7
|
+
inThinking = false;
|
|
8
|
+
/**
|
|
9
|
+
* Feed a chunk of text and get back parsed segments.
|
|
10
|
+
* May return zero or more segments depending on tag boundaries.
|
|
11
|
+
*/
|
|
12
|
+
push(chunk) {
|
|
13
|
+
const results = [];
|
|
14
|
+
let input = this.buffer + chunk;
|
|
15
|
+
this.buffer = "";
|
|
16
|
+
while (input.length > 0) {
|
|
17
|
+
if (this.inThinking) {
|
|
18
|
+
const closeIdx = input.indexOf(CLOSE_TAG);
|
|
19
|
+
if (closeIdx !== -1) {
|
|
20
|
+
// Found close tag — emit thinking content before it
|
|
21
|
+
const thinking = input.slice(0, closeIdx);
|
|
22
|
+
if (thinking)
|
|
23
|
+
results.push({ type: "thinking", text: thinking });
|
|
24
|
+
this.inThinking = false;
|
|
25
|
+
input = input.slice(closeIdx + CLOSE_TAG.length);
|
|
26
|
+
}
|
|
27
|
+
else if (this.couldBePartialTag(input, CLOSE_TAG)) {
|
|
28
|
+
// Possible partial close tag at end — buffer only the matching tail
|
|
29
|
+
const tailLen = this.getPartialTagTailLength(input, CLOSE_TAG);
|
|
30
|
+
const safe = input.slice(0, input.length - tailLen);
|
|
31
|
+
if (safe)
|
|
32
|
+
results.push({ type: "thinking", text: safe });
|
|
33
|
+
this.buffer = input.slice(-tailLen);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// No close tag — emit all as thinking
|
|
38
|
+
results.push({ type: "thinking", text: input });
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const openIdx = input.indexOf(OPEN_TAG);
|
|
44
|
+
if (openIdx !== -1) {
|
|
45
|
+
// Found open tag — emit text before it
|
|
46
|
+
const text = input.slice(0, openIdx);
|
|
47
|
+
if (text)
|
|
48
|
+
results.push({ type: "text", text });
|
|
49
|
+
this.inThinking = true;
|
|
50
|
+
input = input.slice(openIdx + OPEN_TAG.length);
|
|
51
|
+
}
|
|
52
|
+
else if (this.couldBePartialTag(input, OPEN_TAG)) {
|
|
53
|
+
// Possible partial open tag at end — buffer only the matching tail
|
|
54
|
+
const tailLen = this.getPartialTagTailLength(input, OPEN_TAG);
|
|
55
|
+
const safe = input.slice(0, input.length - tailLen);
|
|
56
|
+
if (safe)
|
|
57
|
+
results.push({ type: "text", text: safe });
|
|
58
|
+
this.buffer = input.slice(-tailLen);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// No open tag — emit all as text
|
|
63
|
+
results.push({ type: "text", text: input });
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Flush any remaining buffered content. Call at end of stream.
|
|
72
|
+
*/
|
|
73
|
+
flush() {
|
|
74
|
+
if (!this.buffer)
|
|
75
|
+
return [];
|
|
76
|
+
const result = {
|
|
77
|
+
type: this.inThinking ? "thinking" : "text",
|
|
78
|
+
text: this.buffer,
|
|
79
|
+
};
|
|
80
|
+
this.buffer = "";
|
|
81
|
+
return [result];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if the end of input could be the start of a partial tag.
|
|
85
|
+
* Only buffers when the tail of input matches a prefix of the tag.
|
|
86
|
+
*/
|
|
87
|
+
couldBePartialTag(input, tag) {
|
|
88
|
+
return this.getPartialTagTailLength(input, tag) > 0;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the length of the tail of input that matches a prefix of the tag.
|
|
92
|
+
* Returns 0 if no partial match.
|
|
93
|
+
*/
|
|
94
|
+
getPartialTagTailLength(input, tag) {
|
|
95
|
+
const maxCheck = Math.min(input.length, tag.length - 1);
|
|
96
|
+
for (let len = maxCheck; len >= 1; len--) {
|
|
97
|
+
const tail = input.slice(-len);
|
|
98
|
+
if (tag.startsWith(tail)) {
|
|
99
|
+
return len;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/update-cmd.js
CHANGED
|
@@ -10,18 +10,20 @@ export async function runUpdate() {
|
|
|
10
10
|
const reset = '\x1b[0m';
|
|
11
11
|
process.stdout.write(`${dim}Current version:${reset} v${current}\n`);
|
|
12
12
|
process.stdout.write(`${dim}Checking npm registry...${reset}\n`);
|
|
13
|
-
// Fetch latest version
|
|
13
|
+
// Fetch latest version — bypass npm client cache to avoid stale results (#3445)
|
|
14
14
|
let latest;
|
|
15
15
|
try {
|
|
16
|
-
latest = execSync(`npm view ${NPM_PACKAGE} version`, {
|
|
16
|
+
latest = execSync(`npm view ${NPM_PACKAGE} version --fetch-retry-mintimeout=3000`, {
|
|
17
17
|
encoding: 'utf-8',
|
|
18
18
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
19
|
+
env: { ...process.env, npm_config_cache: '' },
|
|
19
20
|
}).trim();
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
23
|
process.stderr.write(`${yellow}Failed to reach npm registry.${reset}\n`);
|
|
23
24
|
process.exit(1);
|
|
24
25
|
}
|
|
26
|
+
process.stdout.write(`${dim}Latest version:${reset} v${latest}\n`);
|
|
25
27
|
if (compareSemver(latest, current) <= 0) {
|
|
26
28
|
process.stdout.write(`${green}Already up to date.${reset}\n`);
|
|
27
29
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
SDB1T-4NqkMjYirjjqQhr
|