(
transcript: string,
candidates: readonly string[],
aiService: AIService,
options: BuildWorkspaceStatusPromptOptions = {}
)
| 126 | * live provider state rather than guessing from prose. |
| 127 | */ |
| 128 | export async function generateWorkspaceStatus( |
| 129 | transcript: string, |
| 130 | candidates: readonly string[], |
| 131 | aiService: AIService, |
| 132 | options: BuildWorkspaceStatusPromptOptions = {} |
| 133 | ): Promise<Result<GenerateWorkspaceStatusResult, GenerateWorkspaceStatusFailure>> { |
| 134 | if (candidates.length === 0) { |
| 135 | return Err({ |
| 136 | error: { |
| 137 | type: "unknown", |
| 138 | raw: "No model candidates provided for workspace status generation", |
| 139 | }, |
| 140 | reachedProvider: false, |
| 141 | }); |
| 142 | } |
| 143 | |
| 144 | const maxAttempts = Math.min(candidates.length, 3); |
| 145 | let lastError: NameGenerationError | null = null; |
| 146 | // Track whether any candidate's createModel call succeeded — i.e., whether |
| 147 | // we actually crossed the wire to a provider. If every attempt fails at |
| 148 | // construction (no API key, OAuth not connected, provider disabled, etc.), |
| 149 | // the failure is about the user's config rather than the transcript and |
| 150 | // the caller must keep retrying so a later fix recovers. |
| 151 | let reachedProvider = false; |
| 152 | |
| 153 | for (let i = 0; i < maxAttempts; i++) { |
| 154 | const modelString = candidates[i]; |
| 155 | |
| 156 | const modelResult = await aiService.createModel(modelString, undefined, { |
| 157 | agentInitiated: true, |
| 158 | }); |
| 159 | if (!modelResult.success) { |
| 160 | lastError = mapModelCreationError(modelResult.error, modelString); |
| 161 | log.debug(`Status generation: skipping ${modelString} (${modelResult.error.type})`); |
| 162 | continue; |
| 163 | } |
| 164 | reachedProvider = true; |
| 165 | |
| 166 | try { |
| 167 | const currentStream = streamText({ |
| 168 | model: modelResult.data, |
| 169 | prompt: buildWorkspaceStatusPrompt(transcript, options), |
| 170 | tools: { |
| 171 | propose_status: tool({ |
| 172 | description: TOOL_DEFINITIONS.propose_status.description, |
| 173 | inputSchema: ProposeStatusToolArgsSchema, |
| 174 | // eslint-disable-next-line @typescript-eslint/require-await -- AI SDK Tool.execute must return a Promise |
| 175 | execute: async (args) => ({ success: true as const, ...args }), |
| 176 | }), |
| 177 | }, |
| 178 | }); |
| 179 | |
| 180 | const results = await currentStream.toolResults; |
| 181 | const toolResult = results.find((r) => r.dynamic !== true && r.toolName === "propose_status"); |
| 182 | |
| 183 | if (!toolResult) { |
| 184 | lastError = { type: "unknown", raw: "Model did not call propose_status tool" }; |
| 185 | log.warn("Status generation: model did not call propose_status", { modelString }); |
no test coverage detected