@groeponline/pi-wishcraft 0.17.3
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/AGENTS.md +68 -0
- package/CHANGELOG.md +724 -0
- package/CONTRIBUTING.md +37 -0
- package/README.md +648 -0
- package/RELEASE.md +117 -0
- package/ROADMAP.md +52 -0
- package/bash-mode/completion-providers.ts +269 -0
- package/bash-mode/completion.ts +416 -0
- package/bash-mode/editor-ghost.ts +40 -0
- package/bash-mode/editor-input.ts +80 -0
- package/bash-mode/editor.ts +437 -0
- package/bash-mode/history.ts +263 -0
- package/bash-mode/shell-session.ts +286 -0
- package/bash-mode/transcript.ts +108 -0
- package/bash-mode/types.ts +80 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/queue/store.ts +443 -0
- package/queue/types.ts +54 -0
- package/src/config/custom-items.ts +182 -0
- package/src/config/extension-statuses.ts +51 -0
- package/src/config/layout.ts +60 -0
- package/src/config/parse.ts +127 -0
- package/src/config/powerline-config.ts +18 -0
- package/src/config/presets.ts +245 -0
- package/src/config/primitives.ts +117 -0
- package/src/config/segment-ids.ts +114 -0
- package/src/config/segment-options.ts +128 -0
- package/src/config/settings-patch.ts +26 -0
- package/src/config/types.ts +277 -0
- package/src/core/frontmatter.ts +40 -0
- package/src/editor/autocomplete-chain.ts +41 -0
- package/src/extension/activate.ts +28 -0
- package/src/extension/bash-mode-actions.ts +104 -0
- package/src/extension/commands.ts +268 -0
- package/src/extension/constants.ts +46 -0
- package/src/extension/custom-editor.ts +406 -0
- package/src/extension/git-invalidation.ts +40 -0
- package/src/extension/layout.ts +160 -0
- package/src/extension/menu-views.ts +393 -0
- package/src/extension/powerline-widgets.ts +95 -0
- package/src/extension/prompt-history.ts +219 -0
- package/src/extension/queue-commands.ts +245 -0
- package/src/extension/queue-context.ts +12 -0
- package/src/extension/queue-integration.ts +434 -0
- package/src/extension/segment-context.ts +212 -0
- package/src/extension/session-lifecycle.ts +373 -0
- package/src/extension/settings-io.ts +202 -0
- package/src/extension/shortcuts-config.ts +357 -0
- package/src/extension/shortcuts-router.ts +383 -0
- package/src/extension/skills/inline-invocation.ts +174 -0
- package/src/extension/skills/ook.md +6 -0
- package/src/extension/skills/test.md +6 -0
- package/src/extension/stale-context.ts +10 -0
- package/src/extension/stash-history.ts +103 -0
- package/src/extension/state.ts +159 -0
- package/src/extension/status-line-renderers.ts +222 -0
- package/src/extension/types.ts +97 -0
- package/src/extension/vibe-command.ts +160 -0
- package/src/extension/welcome-control.ts +27 -0
- package/src/extension/welcome-integration.ts +153 -0
- package/src/git/status.ts +332 -0
- package/src/paths/agent-dirs.ts +67 -0
- package/src/render/timer.ts +46 -0
- package/src/segments/core.ts +256 -0
- package/src/segments/custom.ts +114 -0
- package/src/segments/index.ts +3 -0
- package/src/segments/registry.ts +87 -0
- package/src/segments/shared.ts +36 -0
- package/src/segments/system.ts +235 -0
- package/src/segments/usage.ts +178 -0
- package/src/shell/cd-command.ts +190 -0
- package/src/shortcuts/matching.ts +61 -0
- package/src/theme/colors.ts +60 -0
- package/src/theme/icons.ts +175 -0
- package/src/theme/separators.ts +41 -0
- package/src/theme/theme.ts +211 -0
- package/src/tools/graph.ts +75 -0
- package/src/tools/patch.ts +179 -0
- package/src/tools/ripgrep.ts +104 -0
- package/src/usage/context.ts +97 -0
- package/src/usage/ledger.ts +293 -0
- package/src/usage/rates.ts +155 -0
- package/src/welcome/auto-dismiss.ts +43 -0
- package/src/welcome/banner.ts +68 -0
- package/src/welcome/discover.ts +234 -0
- package/src/welcome/format.ts +18 -0
- package/src/welcome/index.ts +5 -0
- package/src/welcome/layout.ts +36 -0
- package/src/welcome/overlay.ts +80 -0
- package/src/welcome/renderer.ts +157 -0
- package/src/welcome/sessions.ts +107 -0
- package/src/welcome/types.ts +41 -0
- package/src/welcome/widgets/graph-widget.ts +25 -0
- package/src/welcome/widgets/index.ts +20 -0
- package/src/welcome/widgets/queue-widget.ts +26 -0
- package/src/welcome/widgets/sessions-widget.ts +23 -0
- package/src/welcome/widgets/shortcuts-widget.ts +17 -0
- package/src/welcome/widgets/system-widget.ts +29 -0
- package/src/working-vibes/generate.ts +144 -0
- package/src/working-vibes/index.ts +24 -0
- package/src/working-vibes/manager.ts +198 -0
- package/src/working-vibes/provider.ts +163 -0
- package/src/working-vibes/storage.ts +357 -0
- package/theme.example.json +24 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// manager.ts
|
|
2
|
+
// Public lifecycle API for the working-vibes manager (called from index.ts).
|
|
3
|
+
|
|
4
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import {
|
|
7
|
+
MAX_RECENT_VIBES,
|
|
8
|
+
getNextVibeFromFile,
|
|
9
|
+
getVibeFilePath,
|
|
10
|
+
loadConfig,
|
|
11
|
+
loadVibesFromFile,
|
|
12
|
+
saveConfig,
|
|
13
|
+
saveModeConfig,
|
|
14
|
+
saveModelConfig,
|
|
15
|
+
vibeState,
|
|
16
|
+
type VibeMode,
|
|
17
|
+
} from "./storage.ts";
|
|
18
|
+
import { generateVibe } from "./provider.ts";
|
|
19
|
+
|
|
20
|
+
function trackRecentVibe(vibe: string): void {
|
|
21
|
+
// Don't track fallback messages
|
|
22
|
+
if (vibe === `${vibeState.config.fallback}...`) return;
|
|
23
|
+
|
|
24
|
+
// Add to front, remove duplicates
|
|
25
|
+
vibeState.recentVibes = [
|
|
26
|
+
vibe,
|
|
27
|
+
...vibeState.recentVibes.filter((v) => v !== vibe),
|
|
28
|
+
].slice(0, MAX_RECENT_VIBES);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function updateVibeFromFile(setWorkingMessage: (msg?: string) => void): void {
|
|
32
|
+
setWorkingMessage(getNextVibeFromFile());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function generateAndUpdate(
|
|
36
|
+
prompt: string,
|
|
37
|
+
setWorkingMessage: (msg?: string) => void,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
// File mode: instant, no API call
|
|
40
|
+
if (vibeState.config.mode === "file") {
|
|
41
|
+
updateVibeFromFile(setWorkingMessage);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Generate mode: API call with abort handling
|
|
46
|
+
// Cancel any in-flight generation and create new controller
|
|
47
|
+
// Capture in local variable to avoid race condition with subsequent calls
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
vibeState.currentGeneration?.abort();
|
|
50
|
+
vibeState.currentGeneration = controller;
|
|
51
|
+
|
|
52
|
+
// Create timeout signal (3 seconds)
|
|
53
|
+
const timeoutSignal = AbortSignal.timeout(vibeState.config.timeout);
|
|
54
|
+
const combinedSignal = AbortSignal.any([controller.signal, timeoutSignal]);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const vibe = await generateVibe(
|
|
58
|
+
{ theme: vibeState.config.theme!, userPrompt: prompt },
|
|
59
|
+
combinedSignal,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Only update if still streaming and THIS generation wasn't aborted
|
|
63
|
+
if (vibeState.isStreaming && !controller.signal.aborted) {
|
|
64
|
+
trackRecentVibe(vibe);
|
|
65
|
+
setWorkingMessage(vibe);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// AbortError is expected on timeout/cancel - don't log as error
|
|
69
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
70
|
+
console.debug("[working-vibes] Generation aborted");
|
|
71
|
+
} else {
|
|
72
|
+
console.debug("[working-vibes] Generation failed:", error);
|
|
73
|
+
}
|
|
74
|
+
// Fallback already showing, no action needed
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
79
|
+
// Exported Functions (called from index.ts)
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
+
|
|
82
|
+
export function initVibeManager(ctx: ExtensionContext): void {
|
|
83
|
+
vibeState.extensionCtx = ctx;
|
|
84
|
+
vibeState.config = loadConfig(); // Refresh config in case settings changed
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getVibeTheme(): string | null {
|
|
88
|
+
return vibeState.config.theme;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function setVibeTheme(theme: string | null): boolean {
|
|
92
|
+
vibeState.config = { ...vibeState.config, theme };
|
|
93
|
+
vibeState.recentVibes = []; // Clear recent vibes on theme change
|
|
94
|
+
return saveConfig();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getVibeModel(): string {
|
|
98
|
+
return vibeState.config.modelSpec;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function setVibeModel(modelSpec: string): boolean {
|
|
102
|
+
vibeState.config = { ...vibeState.config, modelSpec };
|
|
103
|
+
return saveModelConfig();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function onVibeBeforeAgentStart(
|
|
107
|
+
prompt: string,
|
|
108
|
+
setWorkingMessage: (msg?: string) => void,
|
|
109
|
+
): void {
|
|
110
|
+
// Skip if no theme configured or no extensionCtx
|
|
111
|
+
if (!vibeState.config.theme || !vibeState.extensionCtx) return;
|
|
112
|
+
|
|
113
|
+
// Queue themed placeholder BEFORE agent_start creates the loader
|
|
114
|
+
// This sets pendingWorkingMessage which is applied when loader is created
|
|
115
|
+
setWorkingMessage(`Channeling ${vibeState.config.theme}...`);
|
|
116
|
+
|
|
117
|
+
// Mark vibe generation time for rate limiting
|
|
118
|
+
vibeState.lastVibeTime = Date.now();
|
|
119
|
+
|
|
120
|
+
// Async: generate and update (fire-and-forget, don't await)
|
|
121
|
+
generateAndUpdate(prompt, setWorkingMessage);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function onVibeAgentStart(): void {
|
|
125
|
+
vibeState.isStreaming = true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function onVibeToolCall(
|
|
129
|
+
toolName: string,
|
|
130
|
+
toolInput: Record<string, unknown>,
|
|
131
|
+
setWorkingMessage: (msg?: string) => void,
|
|
132
|
+
agentContext?: string, // Optional: recent agent response text for richer context
|
|
133
|
+
): void {
|
|
134
|
+
// Skip if no theme, not streaming, or no extensionCtx
|
|
135
|
+
if (
|
|
136
|
+
!vibeState.config.theme ||
|
|
137
|
+
!vibeState.extensionCtx ||
|
|
138
|
+
!vibeState.isStreaming
|
|
139
|
+
)
|
|
140
|
+
return;
|
|
141
|
+
|
|
142
|
+
// Rate limit: skip if not enough time has passed
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
if (now - vibeState.lastVibeTime < vibeState.config.refreshInterval) return;
|
|
145
|
+
|
|
146
|
+
// Prefer agent context if provided (richer, more contextual)
|
|
147
|
+
// Fall back to tool-based hint
|
|
148
|
+
let hint: string;
|
|
149
|
+
if (agentContext && agentContext.length > 10) {
|
|
150
|
+
// Use first ~150 chars of agent context
|
|
151
|
+
hint = agentContext.slice(0, 150);
|
|
152
|
+
} else {
|
|
153
|
+
// Build hint from tool name and input
|
|
154
|
+
hint = `using ${toolName} tool`;
|
|
155
|
+
if (toolName === "read" && toolInput.path) {
|
|
156
|
+
hint = `reading file: ${toolInput.path}`;
|
|
157
|
+
} else if (toolName === "write" && toolInput.path) {
|
|
158
|
+
hint = `writing file: ${toolInput.path}`;
|
|
159
|
+
} else if (toolName === "edit" && toolInput.path) {
|
|
160
|
+
hint = `editing file: ${toolInput.path}`;
|
|
161
|
+
} else if (toolName === "bash" && toolInput.command) {
|
|
162
|
+
const cmd = String(toolInput.command).slice(0, 40);
|
|
163
|
+
hint = `running command: ${cmd}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update time and generate new vibe
|
|
168
|
+
vibeState.lastVibeTime = now;
|
|
169
|
+
generateAndUpdate(hint, setWorkingMessage);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function onVibeAgentEnd(
|
|
173
|
+
setWorkingMessage: (msg?: string) => void,
|
|
174
|
+
): void {
|
|
175
|
+
vibeState.isStreaming = false;
|
|
176
|
+
// Cancel any in-flight generation
|
|
177
|
+
vibeState.currentGeneration?.abort();
|
|
178
|
+
// Reset to pi's default working message
|
|
179
|
+
setWorkingMessage(undefined);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getVibeMode(): VibeMode {
|
|
183
|
+
return vibeState.config.mode;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function setVibeMode(mode: VibeMode): boolean {
|
|
187
|
+
vibeState.config = { ...vibeState.config, mode };
|
|
188
|
+
return saveModeConfig();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function hasVibeFile(theme: string): boolean {
|
|
192
|
+
return existsSync(getVibeFilePath(theme));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getVibeFileCount(theme: string): number {
|
|
196
|
+
const vibes = loadVibesFromFile(theme);
|
|
197
|
+
return vibes.length;
|
|
198
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// provider.ts
|
|
2
|
+
// AI generation: streams from the model provider and turns the response into
|
|
3
|
+
// a themed working message.
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
AssistantMessage,
|
|
7
|
+
Context,
|
|
8
|
+
Model,
|
|
9
|
+
ProviderStreamOptions,
|
|
10
|
+
} from "@earendil-works/pi-ai";
|
|
11
|
+
import { VIBE_SYSTEM_PROMPT, vibeState } from "./storage.ts";
|
|
12
|
+
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
+
// Types
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
|
|
17
|
+
export interface VibeGenContext {
|
|
18
|
+
theme: string;
|
|
19
|
+
userPrompt: string; // from event.prompt in before_agent_start
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Extension-registered providers live in the model registry only: their custom `api` values
|
|
23
|
+
// are absent from pi-ai's global api table, so streaming has to go through the provider.
|
|
24
|
+
// Credential-derived base URLs are resolved per request, mirroring ModelRuntime.prepareRequest;
|
|
25
|
+
// getApiKeyAndHeaders() covers the rest of the request auth but never reports a base URL.
|
|
26
|
+
export async function completeVibe(
|
|
27
|
+
providerId: string,
|
|
28
|
+
model: Model<string>,
|
|
29
|
+
context: Context,
|
|
30
|
+
options: ProviderStreamOptions,
|
|
31
|
+
): Promise<AssistantMessage> {
|
|
32
|
+
const registry = vibeState.extensionCtx?.modelRegistry;
|
|
33
|
+
const provider = registry?.getProvider(providerId);
|
|
34
|
+
if (!registry || !provider) {
|
|
35
|
+
throw new Error(`Provider not registered: ${providerId}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const baseUrl = (await registry.getProviderAuth(providerId))?.auth.baseUrl;
|
|
39
|
+
const requestModel = baseUrl ? { ...model, baseUrl } : model;
|
|
40
|
+
return provider.stream(requestModel, context, options).result();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
// Prompt Building & Response Parsing (Pure Functions)
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
46
|
+
|
|
47
|
+
export function buildVibePrompt(ctx: VibeGenContext): string {
|
|
48
|
+
// Truncate user prompt to save tokens (most context in first 100 chars)
|
|
49
|
+
const task = ctx.userPrompt.slice(0, 100);
|
|
50
|
+
|
|
51
|
+
// Build exclusion list from recent vibes
|
|
52
|
+
const exclude =
|
|
53
|
+
vibeState.recentVibes.length > 0
|
|
54
|
+
? `Don't use: ${vibeState.recentVibes.join(", ")}`
|
|
55
|
+
: "";
|
|
56
|
+
|
|
57
|
+
// Use configured template with variable substitution
|
|
58
|
+
return vibeState.config.promptTemplate
|
|
59
|
+
.replace(/\{theme\}/g, ctx.theme)
|
|
60
|
+
.replace(/\{task\}/g, task)
|
|
61
|
+
.replace(/\{exclude\}/g, exclude);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parseVibeResponse(response: string, fallback: string): string {
|
|
65
|
+
if (!response) return `${fallback}...`;
|
|
66
|
+
|
|
67
|
+
// Take only the first line (AI sometimes adds explanations)
|
|
68
|
+
let vibe = response.trim().split("\n")[0].trim();
|
|
69
|
+
|
|
70
|
+
// Remove quotes if model wrapped the response
|
|
71
|
+
vibe = vibe.replace(/^["']|["']$/g, "");
|
|
72
|
+
|
|
73
|
+
// Ensure ellipsis
|
|
74
|
+
if (!vibe.endsWith("...")) {
|
|
75
|
+
vibe = vibe.replace(/\.+$/, "") + "...";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Enforce length limit (configurable, default 65 chars)
|
|
79
|
+
if (vibe.length > vibeState.config.maxLength) {
|
|
80
|
+
vibe = vibe.slice(0, vibeState.config.maxLength - 3) + "...";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Final validation
|
|
84
|
+
if (!vibe || vibe === "...") {
|
|
85
|
+
return `${fallback}...`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return vibe;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function buildAiContext(prompt: string): Context {
|
|
92
|
+
return {
|
|
93
|
+
systemPrompt: VIBE_SYSTEM_PROMPT,
|
|
94
|
+
messages: [
|
|
95
|
+
{
|
|
96
|
+
role: "user",
|
|
97
|
+
content: [{ type: "text", text: prompt }],
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
105
|
+
// AI Generation
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
export async function generateVibe(
|
|
109
|
+
ctx: VibeGenContext,
|
|
110
|
+
signal: AbortSignal,
|
|
111
|
+
): Promise<string> {
|
|
112
|
+
if (!vibeState.extensionCtx) {
|
|
113
|
+
return `${vibeState.config.fallback}...`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse model spec (provider/modelId format, where modelId may contain slashes)
|
|
117
|
+
const slashIndex = vibeState.config.modelSpec.indexOf("/");
|
|
118
|
+
if (slashIndex === -1) {
|
|
119
|
+
return `${vibeState.config.fallback}...`;
|
|
120
|
+
}
|
|
121
|
+
const provider = vibeState.config.modelSpec.slice(0, slashIndex);
|
|
122
|
+
const modelId = vibeState.config.modelSpec.slice(slashIndex + 1);
|
|
123
|
+
if (!provider || !modelId) {
|
|
124
|
+
return `${vibeState.config.fallback}...`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Resolve model from registry
|
|
128
|
+
const model = vibeState.extensionCtx.modelRegistry.find(provider, modelId);
|
|
129
|
+
if (!model) {
|
|
130
|
+
console.debug(
|
|
131
|
+
`[working-vibes] Model not found: ${vibeState.config.modelSpec}`,
|
|
132
|
+
);
|
|
133
|
+
return `${vibeState.config.fallback}...`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get auth
|
|
137
|
+
const auth =
|
|
138
|
+
await vibeState.extensionCtx.modelRegistry.getApiKeyAndHeaders(model);
|
|
139
|
+
if (!auth.ok) {
|
|
140
|
+
console.debug(`[working-vibes] Auth failed for ${provider}: ${auth.error}`);
|
|
141
|
+
return `${vibeState.config.fallback}...`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const aiContext = buildAiContext(buildVibePrompt(ctx));
|
|
145
|
+
const response = await completeVibe(provider, model, aiContext, {
|
|
146
|
+
apiKey: auth.apiKey,
|
|
147
|
+
headers: auth.headers,
|
|
148
|
+
env: auth.env,
|
|
149
|
+
signal,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
153
|
+
if (
|
|
154
|
+
!textContent?.text &&
|
|
155
|
+
response.stopReason === "error" &&
|
|
156
|
+
response.errorMessage
|
|
157
|
+
) {
|
|
158
|
+
console.debug(
|
|
159
|
+
`[working-vibes] Vibe generation failed for ${vibeState.config.modelSpec}: ${response.errorMessage}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return parseVibeResponse(textContent?.text || "", vibeState.config.fallback);
|
|
163
|
+
}
|