gsd-pi 2.73.0 → 2.73.1-dev.6ddfa43
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/dist/cli.js +0 -47
- package/dist/help-text.js +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +9 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +54 -11
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
- package/dist/resources/extensions/gsd/auto-start.js +20 -6
- package/dist/resources/extensions/gsd/auto.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/crash-log.js +31 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
- package/dist/resources/extensions/gsd/gsd-db.js +36 -2
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
- package/dist/resources/extensions/gsd/preferences-models.js +43 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +22 -0
- package/dist/startup-model-validation.js +8 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/src/index.ts +4 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -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 +27 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +175 -8
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -68
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +12 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +51 -26
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +95 -21
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +198 -8
- package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -70
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +62 -26
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +115 -26
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -4
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +23 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +85 -11
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
- package/src/resources/extensions/gsd/auto-start.ts +27 -6
- package/src/resources/extensions/gsd/auto.ts +5 -0
- package/src/resources/extensions/gsd/bootstrap/crash-log.ts +32 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -7
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
- package/src/resources/extensions/gsd/gsd-db.ts +52 -2
- package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
- package/src/resources/extensions/gsd/preferences-models.ts +41 -0
- package/src/resources/extensions/gsd/preferences-types.ts +12 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +23 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +51 -2
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +137 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +59 -1
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +91 -2
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → r6AvNu-aMwn4nwqjHqAfw}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KSZ2dcC3p4z6lOmUpPpzr → r6AvNu-aMwn4nwqjHqAfw}/_ssgManifest.js +0 -0
|
@@ -3,45 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ThinkingLevel } from "@gsd/pi-agent-core";
|
|
6
|
-
import { type Api, type
|
|
6
|
+
import { type Api, type Model, modelsAreEqual } from "@gsd/pi-ai";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { minimatch } from "minimatch";
|
|
9
9
|
import { isValidThinkingLevel } from "../cli/args.js";
|
|
10
10
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
11
11
|
import type { ModelRegistry } from "./model-registry.js";
|
|
12
12
|
|
|
13
|
-
/** Default model IDs for each known provider */
|
|
14
|
-
const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|
15
|
-
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
16
|
-
anthropic: "claude-opus-4-6",
|
|
17
|
-
"anthropic-vertex": "claude-sonnet-4-6",
|
|
18
|
-
openai: "gpt-5.4",
|
|
19
|
-
"azure-openai-responses": "gpt-5.2",
|
|
20
|
-
"openai-codex": "gpt-5.4",
|
|
21
|
-
google: "gemini-2.5-pro",
|
|
22
|
-
"google-gemini-cli": "gemini-2.5-pro",
|
|
23
|
-
"google-antigravity": "gemini-3.1-pro-high",
|
|
24
|
-
"google-vertex": "gemini-3-pro-preview",
|
|
25
|
-
"github-copilot": "gpt-4o",
|
|
26
|
-
openrouter: "openai/gpt-5.1-codex",
|
|
27
|
-
"vercel-ai-gateway": "anthropic/claude-opus-4-6",
|
|
28
|
-
xai: "grok-4-fast-non-reasoning",
|
|
29
|
-
groq: "openai/gpt-oss-120b",
|
|
30
|
-
cerebras: "zai-glm-4.6",
|
|
31
|
-
zai: "glm-4.6",
|
|
32
|
-
mistral: "devstral-medium-latest",
|
|
33
|
-
minimax: "MiniMax-M2.1",
|
|
34
|
-
"minimax-cn": "MiniMax-M2.1",
|
|
35
|
-
huggingface: "moonshotai/Kimi-K2.5",
|
|
36
|
-
opencode: "claude-opus-4-6",
|
|
37
|
-
"opencode-go": "kimi-k2.5",
|
|
38
|
-
"kimi-coding": "kimi-k2-thinking",
|
|
39
|
-
"alibaba-coding-plan": "qwen3.5-plus",
|
|
40
|
-
"alibaba-dashscope": "qwen3.5-plus",
|
|
41
|
-
ollama: "llama3.1:8b",
|
|
42
|
-
"ollama-cloud": "qwen3:32b",
|
|
43
|
-
};
|
|
44
|
-
|
|
45
13
|
export interface ScopedModel {
|
|
46
14
|
model: Model<Api>;
|
|
47
15
|
/** Thinking level if explicitly specified in pattern (e.g., "model:high"), undefined otherwise */
|
|
@@ -123,10 +91,11 @@ function buildFallbackModel(provider: string, modelId: string, availableModels:
|
|
|
123
91
|
const providerModels = availableModels.filter((m) => m.provider === provider);
|
|
124
92
|
if (providerModels.length === 0) return undefined;
|
|
125
93
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
94
|
+
// Use the first available model from this provider as a template for
|
|
95
|
+
// capabilities (context window, reasoning support, etc.). The user is
|
|
96
|
+
// explicitly providing a custom model id, so we just need any shape of
|
|
97
|
+
// model from the same provider to inherit from.
|
|
98
|
+
const baseModel = providerModels[0];
|
|
130
99
|
|
|
131
100
|
return {
|
|
132
101
|
...baseModel,
|
|
@@ -503,33 +472,19 @@ export async function findInitialModel(options: {
|
|
|
503
472
|
};
|
|
504
473
|
}
|
|
505
474
|
|
|
506
|
-
// 3. Try saved default from settings
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (recommendedId && recommendedId !== defaultModelId && recommendedId.startsWith(defaultModelId)) {
|
|
518
|
-
const recommended = modelRegistry.find(defaultProvider, recommendedId);
|
|
519
|
-
if (recommended) {
|
|
520
|
-
model = recommended;
|
|
521
|
-
if (defaultThinkingLevel) {
|
|
522
|
-
thinkingLevel = defaultThinkingLevel;
|
|
523
|
-
}
|
|
524
|
-
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
model = found;
|
|
528
|
-
if (defaultThinkingLevel) {
|
|
529
|
-
thinkingLevel = defaultThinkingLevel;
|
|
530
|
-
}
|
|
531
|
-
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
475
|
+
// 3. Try saved default from settings — use it exactly as configured.
|
|
476
|
+
// Whatever the user chose is what gets used; no silent substitution.
|
|
477
|
+
// Skip the saved default if its provider is not request-ready (no auth
|
|
478
|
+
// available) so we fall through to an actually-usable model instead of
|
|
479
|
+
// returning a stale selection every selector surface would display.
|
|
480
|
+
if (defaultProvider && defaultModelId && modelRegistry.isProviderRequestReady(defaultProvider)) {
|
|
481
|
+
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
|
482
|
+
if (found) {
|
|
483
|
+
model = found;
|
|
484
|
+
if (defaultThinkingLevel) {
|
|
485
|
+
thinkingLevel = defaultThinkingLevel;
|
|
532
486
|
}
|
|
487
|
+
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
533
488
|
}
|
|
534
489
|
}
|
|
535
490
|
|
|
@@ -537,16 +492,17 @@ export async function findInitialModel(options: {
|
|
|
537
492
|
const availableModels = await modelRegistry.getAvailable();
|
|
538
493
|
|
|
539
494
|
if (availableModels.length > 0) {
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
if (
|
|
545
|
-
return { model:
|
|
495
|
+
// Prefer a model from the user's saved provider if any is still available —
|
|
496
|
+
// provider stickiness, not a hard-coded Anthropic/OpenAI preference.
|
|
497
|
+
if (defaultProvider) {
|
|
498
|
+
const sameProvider = availableModels.find((m) => m.provider === defaultProvider);
|
|
499
|
+
if (sameProvider) {
|
|
500
|
+
return { model: sameProvider, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
|
546
501
|
}
|
|
547
502
|
}
|
|
548
503
|
|
|
549
|
-
//
|
|
504
|
+
// Otherwise use the first available — registry order reflects models.json
|
|
505
|
+
// order, which the user controls.
|
|
550
506
|
return { model: availableModels[0], thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
|
551
507
|
}
|
|
552
508
|
|
|
@@ -3,8 +3,15 @@ import { Container, Markdown, type MarkdownTheme, Spacer, Text } from "@gsd/pi-t
|
|
|
3
3
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
4
4
|
import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
|
|
5
5
|
|
|
6
|
+
export interface ContentRange {
|
|
7
|
+
startIndex: number;
|
|
8
|
+
endIndex: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
|
-
* Component that renders a complete assistant message
|
|
12
|
+
* Component that renders a complete assistant message, or a sub-range of its content[].
|
|
13
|
+
* When `range` is provided, only content[startIndex..endIndex] (inclusive) is rendered.
|
|
14
|
+
* Non-text/thinking blocks within the range are silently skipped.
|
|
8
15
|
*/
|
|
9
16
|
export class AssistantMessageComponent extends Container {
|
|
10
17
|
private contentContainer: Container;
|
|
@@ -12,18 +19,26 @@ export class AssistantMessageComponent extends Container {
|
|
|
12
19
|
private markdownTheme: MarkdownTheme;
|
|
13
20
|
private lastMessage?: AssistantMessage;
|
|
14
21
|
private timestampFormat: TimestampFormat;
|
|
22
|
+
private range?: ContentRange;
|
|
23
|
+
private showMetadata: boolean;
|
|
15
24
|
|
|
16
25
|
constructor(
|
|
17
26
|
message?: AssistantMessage,
|
|
18
27
|
hideThinkingBlock = false,
|
|
19
28
|
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
|
20
29
|
timestampFormat: TimestampFormat = "date-time-iso",
|
|
30
|
+
range?: ContentRange,
|
|
21
31
|
) {
|
|
22
32
|
super();
|
|
23
33
|
|
|
24
34
|
this.hideThinkingBlock = hideThinkingBlock;
|
|
25
35
|
this.markdownTheme = markdownTheme;
|
|
26
36
|
this.timestampFormat = timestampFormat;
|
|
37
|
+
this.range = range;
|
|
38
|
+
// No range = legacy full-message rendering; show metadata by default.
|
|
39
|
+
// Ranged (interleaved) instances start with metadata hidden; chat-controller
|
|
40
|
+
// calls setShowMetadata(true) on the last segment at message_end.
|
|
41
|
+
this.showMetadata = !range;
|
|
27
42
|
|
|
28
43
|
// Container for text/thinking content
|
|
29
44
|
this.contentContainer = new Container();
|
|
@@ -34,6 +49,20 @@ export class AssistantMessageComponent extends Container {
|
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
|
|
52
|
+
setRange(range: ContentRange | undefined): void {
|
|
53
|
+
this.range = range;
|
|
54
|
+
if (this.lastMessage) {
|
|
55
|
+
this.updateContent(this.lastMessage);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setShowMetadata(show: boolean): void {
|
|
60
|
+
this.showMetadata = show;
|
|
61
|
+
if (this.lastMessage) {
|
|
62
|
+
this.updateContent(this.lastMessage);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
override invalidate(): void {
|
|
38
67
|
super.invalidate();
|
|
39
68
|
if (this.lastMessage) {
|
|
@@ -51,7 +80,11 @@ export class AssistantMessageComponent extends Container {
|
|
|
51
80
|
// Clear content container
|
|
52
81
|
this.contentContainer.clear();
|
|
53
82
|
|
|
54
|
-
const
|
|
83
|
+
const start = this.range?.startIndex ?? 0;
|
|
84
|
+
const end = this.range?.endIndex ?? message.content.length - 1;
|
|
85
|
+
const slice = message.content.slice(start, end + 1);
|
|
86
|
+
|
|
87
|
+
const hasVisibleContent = slice.some(
|
|
55
88
|
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
|
56
89
|
);
|
|
57
90
|
|
|
@@ -59,9 +92,9 @@ export class AssistantMessageComponent extends Container {
|
|
|
59
92
|
this.contentContainer.addChild(new Spacer(1));
|
|
60
93
|
}
|
|
61
94
|
|
|
62
|
-
// Render content in order
|
|
63
|
-
for (let i = 0; i <
|
|
64
|
-
const content =
|
|
95
|
+
// Render content in order; non-text/thinking blocks are silently skipped
|
|
96
|
+
for (let i = 0; i < slice.length; i++) {
|
|
97
|
+
const content = slice[i];
|
|
65
98
|
if (content.type === "text" && content.text.trim()) {
|
|
66
99
|
// Assistant text messages with no background - trim the text
|
|
67
100
|
// Set paddingY=0 to avoid extra spacing before tool executions
|
|
@@ -69,7 +102,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
69
102
|
} else if (content.type === "thinking" && content.thinking.trim()) {
|
|
70
103
|
// Add spacing only when another visible assistant content block follows.
|
|
71
104
|
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
|
|
72
|
-
const hasVisibleContentAfter =
|
|
105
|
+
const hasVisibleContentAfter = slice
|
|
73
106
|
.slice(i + 1)
|
|
74
107
|
.some((c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
|
|
75
108
|
|
|
@@ -94,30 +127,33 @@ export class AssistantMessageComponent extends Container {
|
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
129
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
// Metadata (errors, timestamp): gated on showMetadata so ranged instances stay clean
|
|
131
|
+
// until chat-controller explicitly enables it on the last segment at message_end.
|
|
132
|
+
if (this.showMetadata) {
|
|
133
|
+
// Check if aborted - show after partial content
|
|
134
|
+
// But only if there are no tool calls (tool execution components will show the error)
|
|
135
|
+
const hasToolCalls = message.content.some((c) => c.type === "toolCall");
|
|
136
|
+
if (!hasToolCalls) {
|
|
137
|
+
if (message.stopReason === "aborted") {
|
|
138
|
+
const abortMessage =
|
|
139
|
+
message.errorMessage && message.errorMessage !== "Request was aborted"
|
|
140
|
+
? message.errorMessage
|
|
141
|
+
: "Operation aborted";
|
|
142
|
+
if (hasVisibleContent) {
|
|
143
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
144
|
+
}
|
|
145
|
+
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
|
146
|
+
} else if (message.stopReason === "error") {
|
|
147
|
+
const errorMsg = message.errorMessage || "Unknown error";
|
|
107
148
|
this.contentContainer.addChild(new Spacer(1));
|
|
149
|
+
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
108
150
|
}
|
|
109
|
-
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
|
110
|
-
} else if (message.stopReason === "error") {
|
|
111
|
-
const errorMsg = message.errorMessage || "Unknown error";
|
|
112
|
-
this.contentContainer.addChild(new Spacer(1));
|
|
113
|
-
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
114
151
|
}
|
|
115
|
-
}
|
|
116
152
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
if (message.stopReason && message.timestamp) {
|
|
154
|
+
const timeStr = formatTimestamp(message.timestamp, this.timestampFormat);
|
|
155
|
+
this.contentContainer.addChild(new Text(theme.fg("dim", timeStr), 1, 0));
|
|
156
|
+
}
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
159
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { findLatestPinnableText } from "./chat-controller.js";
|
|
5
|
+
|
|
6
|
+
test("findLatestPinnableText: empty content returns empty string", () => {
|
|
7
|
+
assert.equal(findLatestPinnableText([]), "");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("findLatestPinnableText: no tool calls returns empty string", () => {
|
|
11
|
+
const blocks = [
|
|
12
|
+
{ type: "text", text: "hello" },
|
|
13
|
+
{ type: "text", text: "world" },
|
|
14
|
+
];
|
|
15
|
+
assert.equal(findLatestPinnableText(blocks), "");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("findLatestPinnableText: returns text preceding a tool call", () => {
|
|
19
|
+
const blocks = [
|
|
20
|
+
{ type: "text", text: "doing the thing" },
|
|
21
|
+
{ type: "toolCall", id: "1", name: "Read" },
|
|
22
|
+
];
|
|
23
|
+
assert.equal(findLatestPinnableText(blocks), "doing the thing");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("findLatestPinnableText: ignores trailing streaming text after the last tool call (regression: pinned mirror duplicated chat-container tokens)", () => {
|
|
27
|
+
const blocks = [
|
|
28
|
+
{ type: "text", text: "first prose" },
|
|
29
|
+
{ type: "toolCall", id: "1", name: "Read" },
|
|
30
|
+
{ type: "text", text: "second prose still streaming" },
|
|
31
|
+
];
|
|
32
|
+
assert.equal(findLatestPinnableText(blocks), "first prose");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("findLatestPinnableText: with multiple tools, picks text before the most recent tool call", () => {
|
|
36
|
+
const blocks = [
|
|
37
|
+
{ type: "text", text: "first" },
|
|
38
|
+
{ type: "toolCall", id: "1", name: "Read" },
|
|
39
|
+
{ type: "text", text: "second" },
|
|
40
|
+
{ type: "toolCall", id: "2", name: "Grep" },
|
|
41
|
+
{ type: "text", text: "third streaming" },
|
|
42
|
+
];
|
|
43
|
+
assert.equal(findLatestPinnableText(blocks), "second");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("findLatestPinnableText: treats serverToolUse the same as toolCall", () => {
|
|
47
|
+
const blocks = [
|
|
48
|
+
{ type: "text", text: "before web search" },
|
|
49
|
+
{ type: "serverToolUse", id: "ws1", name: "web_search" },
|
|
50
|
+
{ type: "text", text: "answer streaming" },
|
|
51
|
+
];
|
|
52
|
+
assert.equal(findLatestPinnableText(blocks), "before web search");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("findLatestPinnableText: skips empty/whitespace-only text blocks", () => {
|
|
56
|
+
const blocks = [
|
|
57
|
+
{ type: "text", text: "real prose" },
|
|
58
|
+
{ type: "text", text: " " },
|
|
59
|
+
{ type: "text", text: "" },
|
|
60
|
+
{ type: "toolCall", id: "1", name: "Read" },
|
|
61
|
+
];
|
|
62
|
+
assert.equal(findLatestPinnableText(blocks), "real prose");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("findLatestPinnableText: thinking blocks are not pinnable", () => {
|
|
66
|
+
const blocks = [
|
|
67
|
+
{ type: "thinking", thinking: "internal" },
|
|
68
|
+
{ type: "toolCall", id: "1", name: "Read" },
|
|
69
|
+
];
|
|
70
|
+
assert.equal(findLatestPinnableText(blocks), "");
|
|
71
|
+
});
|
|
@@ -10,6 +10,13 @@ import { appKey } from "../components/keybinding-hints.js";
|
|
|
10
10
|
// Tracks the last processed content index to avoid re-scanning all blocks on every message_update
|
|
11
11
|
let lastProcessedContentIndex = 0;
|
|
12
12
|
|
|
13
|
+
// --- Segment walker state (per streaming assistant turn) ---
|
|
14
|
+
type RenderedSegment =
|
|
15
|
+
| { kind: "text-run"; startIndex: number; endIndex: number; component: AssistantMessageComponent }
|
|
16
|
+
| { kind: "tool"; contentIndex: number; component: ToolExecutionComponent };
|
|
17
|
+
|
|
18
|
+
let renderedSegments: RenderedSegment[] = [];
|
|
19
|
+
|
|
13
20
|
function hasVisibleAssistantContent(message: { content: Array<any> }): boolean {
|
|
14
21
|
return message.content.some(
|
|
15
22
|
(c) =>
|
|
@@ -22,6 +29,28 @@ function hasAssistantToolBlocks(message: { content: Array<any> }): boolean {
|
|
|
22
29
|
return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
// Pick the latest non-empty text block that appears strictly before the most
|
|
33
|
+
// recent tool call. Text blocks that come after the last tool call are still
|
|
34
|
+
// streaming live into the chat container, so mirroring them into the pinned
|
|
35
|
+
// "Latest Output" zone would render the same tokens twice.
|
|
36
|
+
export function findLatestPinnableText(contentBlocks: Array<any>): string {
|
|
37
|
+
let lastToolIdx = -1;
|
|
38
|
+
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
|
39
|
+
const c = contentBlocks[i];
|
|
40
|
+
if (c?.type === "toolCall" || c?.type === "serverToolUse") {
|
|
41
|
+
lastToolIdx = i;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (let i = lastToolIdx - 1; i >= 0; i--) {
|
|
46
|
+
const c = contentBlocks[i];
|
|
47
|
+
if (c?.type === "text" && typeof c.text === "string" && c.text.trim()) {
|
|
48
|
+
return c.text.trim();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
// Tracks the latest assistant text for the pinned message zone
|
|
26
55
|
let lastPinnedText = "";
|
|
27
56
|
// Whether any tool execution has been added in this assistant turn (triggers pinned display)
|
|
@@ -58,6 +87,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
58
87
|
lastProcessedContentIndex = 0;
|
|
59
88
|
lastPinnedText = "";
|
|
60
89
|
hasToolsInTurn = false;
|
|
90
|
+
renderedSegments = [];
|
|
61
91
|
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
62
92
|
pinnedBorder = undefined;
|
|
63
93
|
pinnedTextComponent = undefined;
|
|
@@ -77,6 +107,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
77
107
|
host.pinnedMessageContainer.clear();
|
|
78
108
|
lastPinnedText = "";
|
|
79
109
|
hasToolsInTurn = false;
|
|
110
|
+
renderedSegments = [];
|
|
80
111
|
if (pinnedBorder) pinnedBorder.stopSpinner();
|
|
81
112
|
pinnedBorder = undefined;
|
|
82
113
|
pinnedTextComponent = undefined;
|
|
@@ -251,24 +282,88 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
251
282
|
}
|
|
252
283
|
}
|
|
253
284
|
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
// Segment walker: render content blocks in stream order, append-only.
|
|
286
|
+
// Build desired segment plan from content[].
|
|
287
|
+
{
|
|
288
|
+
const blocks = host.streamingMessage.content;
|
|
289
|
+
type DesiredSegment =
|
|
290
|
+
| { kind: "text-run"; startIndex: number; endIndex: number }
|
|
291
|
+
| { kind: "tool"; contentIndex: number; toolId: string };
|
|
292
|
+
const desired: DesiredSegment[] = [];
|
|
293
|
+
let runStart = -1;
|
|
294
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
295
|
+
const b = blocks[i];
|
|
296
|
+
const isText = b.type === "text" || b.type === "thinking";
|
|
297
|
+
const isTool = b.type === "toolCall" || b.type === "serverToolUse";
|
|
298
|
+
if (isText) {
|
|
299
|
+
if (runStart === -1) runStart = i;
|
|
300
|
+
} else {
|
|
301
|
+
if (runStart !== -1) {
|
|
302
|
+
desired.push({ kind: "text-run", startIndex: runStart, endIndex: i - 1 });
|
|
303
|
+
runStart = -1;
|
|
304
|
+
}
|
|
305
|
+
if (isTool) {
|
|
306
|
+
desired.push({ kind: "tool", contentIndex: i, toolId: b.id });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (runStart !== -1) {
|
|
311
|
+
desired.push({ kind: "text-run", startIndex: runStart, endIndex: blocks.length - 1 });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Append any newly needed segments (never reorder existing ones).
|
|
315
|
+
for (const seg of desired) {
|
|
316
|
+
if (seg.kind === "tool") {
|
|
317
|
+
// Tool segments are already handled above via pendingTools; just
|
|
318
|
+
// register them in renderedSegments if not yet tracked.
|
|
319
|
+
const existing = renderedSegments.find(
|
|
320
|
+
(s) => s.kind === "tool" && s.contentIndex === seg.contentIndex,
|
|
321
|
+
);
|
|
322
|
+
if (!existing) {
|
|
323
|
+
const comp = host.pendingTools.get(seg.toolId);
|
|
324
|
+
if (comp) {
|
|
325
|
+
renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component: comp });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
// text-run segment
|
|
330
|
+
const existing = renderedSegments.find(
|
|
331
|
+
(s) => s.kind === "text-run" && s.startIndex === seg.startIndex,
|
|
332
|
+
);
|
|
333
|
+
if (!existing) {
|
|
334
|
+
const comp = new AssistantMessageComponent(
|
|
335
|
+
undefined,
|
|
336
|
+
host.hideThinkingBlock,
|
|
337
|
+
host.getMarkdownThemeWithSettings(),
|
|
338
|
+
host.settingsManager.getTimestampFormat(),
|
|
339
|
+
{ startIndex: seg.startIndex, endIndex: seg.endIndex },
|
|
340
|
+
);
|
|
341
|
+
host.chatContainer.addChild(comp);
|
|
342
|
+
renderedSegments.push({ kind: "text-run", startIndex: seg.startIndex, endIndex: seg.endIndex, component: comp });
|
|
343
|
+
host.streamingComponent = comp;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Update all trailing text-run segments with the latest message so
|
|
349
|
+
// streaming text grows in place.
|
|
350
|
+
for (const seg of renderedSegments) {
|
|
351
|
+
if (seg.kind === "text-run") {
|
|
352
|
+
// Find corresponding desired segment to get current endIndex
|
|
353
|
+
const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex);
|
|
354
|
+
if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
|
|
355
|
+
seg.endIndex = d.endIndex;
|
|
356
|
+
seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
|
|
357
|
+
}
|
|
358
|
+
seg.component.updateContent(host.streamingMessage);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Keep streamingComponent pointing at the last text-run for message_end compatibility.
|
|
363
|
+
const lastTextSeg = [...renderedSegments].reverse().find((s) => s.kind === "text-run");
|
|
364
|
+
if (lastTextSeg && lastTextSeg.kind === "text-run") {
|
|
365
|
+
host.streamingComponent = lastTextSeg.component;
|
|
270
366
|
}
|
|
271
|
-
host.streamingComponent.updateContent(host.streamingMessage);
|
|
272
367
|
}
|
|
273
368
|
|
|
274
369
|
// Update index: fully processed blocks won't need re-scanning.
|
|
@@ -286,15 +381,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
286
381
|
if (hasTools) hasToolsInTurn = true;
|
|
287
382
|
|
|
288
383
|
if (hasToolsInTurn) {
|
|
289
|
-
|
|
290
|
-
let latestText = "";
|
|
291
|
-
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
|
292
|
-
const c = contentBlocks[i] as any;
|
|
293
|
-
if (c.type === "text" && c.text?.trim()) {
|
|
294
|
-
latestText = c.text.trim();
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
384
|
+
const latestText = findLatestPinnableText(contentBlocks);
|
|
298
385
|
|
|
299
386
|
if (latestText && latestText !== lastPinnedText) {
|
|
300
387
|
lastPinnedText = latestText;
|
|
@@ -362,6 +449,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
362
449
|
host.chatContainer.addChild(host.streamingComponent);
|
|
363
450
|
}
|
|
364
451
|
if (host.streamingComponent) {
|
|
452
|
+
host.streamingComponent.setShowMetadata(true);
|
|
365
453
|
host.streamingComponent.updateContent(host.streamingMessage);
|
|
366
454
|
}
|
|
367
455
|
|
|
@@ -385,6 +473,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
385
473
|
}
|
|
386
474
|
host.streamingComponent = undefined;
|
|
387
475
|
host.streamingMessage = undefined;
|
|
476
|
+
renderedSegments = [];
|
|
388
477
|
// Clear pinned output once the message is finalized in the chat
|
|
389
478
|
// container — prevents duplicate display when the agent continues
|
|
390
479
|
// (e.g. form elicitation) after the assistant message ends.
|
package/pkg/package.json
CHANGED
|
@@ -14,10 +14,11 @@ import type {
|
|
|
14
14
|
Context,
|
|
15
15
|
Model,
|
|
16
16
|
SimpleStreamOptions,
|
|
17
|
+
ThinkingLevel,
|
|
17
18
|
ToolCall,
|
|
18
19
|
} from "@gsd/pi-ai";
|
|
19
20
|
import type { ExtensionUIContext } from "@gsd/pi-coding-agent";
|
|
20
|
-
import { EventStream } from "@gsd/pi-ai";
|
|
21
|
+
import { EventStream, mapThinkingLevelToEffort, supportsAdaptiveThinking } from "@gsd/pi-ai";
|
|
21
22
|
import { execSync } from "node:child_process";
|
|
22
23
|
import { PartialMessageBuilder, ZERO_USAGE, mapUsage } from "./partial-builder.js";
|
|
23
24
|
import { buildWorkflowMcpServers } from "../gsd/workflow-mcp.js";
|
|
@@ -600,8 +601,9 @@ export function buildSdkOptions(
|
|
|
600
601
|
modelId: string,
|
|
601
602
|
prompt: string,
|
|
602
603
|
overrides?: { permissionMode?: "bypassPermissions" | "acceptEdits" | "default" | "plan" },
|
|
603
|
-
extraOptions: Record<string, unknown> = {},
|
|
604
|
+
extraOptions: Record<string, unknown> & { reasoning?: ThinkingLevel } = {},
|
|
604
605
|
): Record<string, unknown> {
|
|
606
|
+
const { reasoning, ...sdkExtraOptions } = extraOptions;
|
|
605
607
|
const mcpServers = buildWorkflowMcpServers();
|
|
606
608
|
const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
|
|
607
609
|
const disallowedTools = ["AskUserQuestion"];
|
|
@@ -620,6 +622,10 @@ export function buildSdkOptions(
|
|
|
620
622
|
"Bash(pwd)",
|
|
621
623
|
...(mcpServers ? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`) : []),
|
|
622
624
|
];
|
|
625
|
+
const effort =
|
|
626
|
+
reasoning && supportsAdaptiveThinking(modelId)
|
|
627
|
+
? mapThinkingLevelToEffort(reasoning, modelId)
|
|
628
|
+
: undefined;
|
|
623
629
|
return {
|
|
624
630
|
pathToClaudeCodeExecutable: getClaudePath(),
|
|
625
631
|
model: modelId,
|
|
@@ -634,7 +640,8 @@ export function buildSdkOptions(
|
|
|
634
640
|
...(allowedTools.length > 0 ? { allowedTools } : {}),
|
|
635
641
|
...(mcpServers ? { mcpServers } : {}),
|
|
636
642
|
betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
|
|
637
|
-
...
|
|
643
|
+
...(effort ? { effort } : {}),
|
|
644
|
+
...sdkExtraOptions,
|
|
638
645
|
};
|
|
639
646
|
}
|
|
640
647
|
|
|
@@ -828,11 +835,12 @@ async function pumpSdkMessages(
|
|
|
828
835
|
{ permissionMode },
|
|
829
836
|
typeof (options as ClaudeCodeStreamOptions | undefined)?.extensionUIContext === "object"
|
|
830
837
|
? {
|
|
838
|
+
reasoning: options?.reasoning,
|
|
831
839
|
onElicitation: createClaudeCodeElicitationHandler(
|
|
832
840
|
(options as ClaudeCodeStreamOptions | undefined)?.extensionUIContext,
|
|
833
841
|
),
|
|
834
842
|
}
|
|
835
|
-
: {},
|
|
843
|
+
: { reasoning: options?.reasoning },
|
|
836
844
|
);
|
|
837
845
|
|
|
838
846
|
const queryResult = sdk.query({
|