@fgv/ts-app-shell 5.1.0-34 → 5.1.0-35

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.
@@ -178,12 +178,15 @@ export function useAiAssist(params) {
178
178
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
179
179
  // Resolve effective tools: per-call override > settings defaults > none
180
180
  const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);
181
- // Call the API — through proxy if configured, otherwise direct
181
+ // Call the API — through proxy if configured, otherwise direct.
182
+ // The unified request orders history before the current turn; the
183
+ // correction-retry turns naturally append after the current user turn.
184
+ const base = prompt.toRequest();
182
185
  const completionParams = {
183
186
  descriptor,
184
187
  apiKey: apiKeyResult.value,
185
- prompt,
186
- additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
188
+ system: base.system,
189
+ messages: [...base.messages, ...correctionMessages],
187
190
  modelOverride: providerConfig.model,
188
191
  endpoint: providerConfig.endpoint,
189
192
  logger,
@@ -316,6 +319,7 @@ export function useAiAssist(params) {
316
319
  }, [settings, keyStore, logger]);
317
320
  const streamDirect = useCallback(async (provider, prompt, onEvent, options) => {
318
321
  var _a, e_1, _b, _c;
322
+ var _d;
319
323
  const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
320
324
  if (!providerConfig) {
321
325
  return fail(`Provider "${provider}" not configured`);
@@ -330,11 +334,13 @@ export function useAiAssist(params) {
330
334
  return fail(apiKeyResult.message);
331
335
  }
332
336
  const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options === null || options === void 0 ? void 0 : options.tools);
337
+ // Order prior history before the current user turn (the unified request shape).
338
+ const base = prompt.toRequest();
333
339
  const requestParams = {
334
340
  descriptor,
335
341
  apiKey: apiKeyResult.value,
336
- prompt,
337
- messagesBefore: options === null || options === void 0 ? void 0 : options.messagesBefore,
342
+ system: base.system,
343
+ messages: [...((_d = options === null || options === void 0 ? void 0 : options.messagesBefore) !== null && _d !== void 0 ? _d : []), ...base.messages],
338
344
  modelOverride: providerConfig.model,
339
345
  endpoint: providerConfig.endpoint,
340
346
  logger,
@@ -356,9 +362,9 @@ export function useAiAssist(params) {
356
362
  let receivedTerminalEvent = false;
357
363
  try {
358
364
  try {
359
- for (var _d = true, _e = __asyncValues(openResult.value), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
360
- _c = _f.value;
361
- _d = false;
365
+ for (var _e = true, _f = __asyncValues(openResult.value), _g; _g = await _f.next(), _a = _g.done, !_a; _e = true) {
366
+ _c = _g.value;
367
+ _e = false;
362
368
  const event = _c;
363
369
  try {
364
370
  onEvent(event);
@@ -388,7 +394,7 @@ export function useAiAssist(params) {
388
394
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
389
395
  finally {
390
396
  try {
391
- if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
397
+ if (!_e && !_a && (_b = _f.return)) await _b.call(_f);
392
398
  }
393
399
  finally { if (e_1) throw e_1.error; }
394
400
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useAiAssist.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.\n *\n * Provides a list of available AI assist actions and functions for copy-paste\n * and direct API generation flows.\n *\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { AiAssist } from '@fgv/ts-extras';\nimport { fail, type Logging, Result, succeed } from '@fgv/ts-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Parameters for the generic useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistParams {\n /** Resolved AI assist settings (provider list + default). */\n readonly settings: AiAssist.IAiAssistSettings | undefined;\n /** Keystore for API key resolution (or undefined if no keystore). */\n readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * A single available AI assist action.\n * @public\n */\nexport interface IAiAssistAction {\n /** The provider identifier */\n readonly provider: AiAssist.AiProviderId;\n /** Display label (e.g. \"AI Assist | Copy\", \"AI Assist | Grok\") */\n readonly label: string;\n /** Whether this is the default (first) action */\n readonly isDefault: boolean;\n /** Whether the provider is currently available (keystore unlocked, secret present) */\n readonly isAvailable: boolean;\n /** Reason the provider is unavailable, if any */\n readonly unavailableReason?: string;\n}\n\n/**\n * Result of executing an AI assist action.\n * @public\n */\nexport interface IAiAssistResult<TEntity> {\n /** The generated entity */\n readonly entity: TEntity;\n /** Source indicator */\n readonly source: 'ai';\n}\n\n/**\n * Return type of the useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistResult {\n /** Available actions based on settings + keystore state */\n readonly actions: ReadonlyArray<IAiAssistAction>;\n /** Whether a direct assist call is in progress */\n readonly isWorking: boolean;\n /**\n * Execute a copy-paste action: copies the combined prompt to clipboard.\n * @returns Success with `'copied'`, or failure.\n */\n readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;\n /**\n * Execute a direct API action: calls the provider, validates the response, returns the entity.\n * @returns Success with the validated entity, or failure.\n */\n readonly generateDirect: <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ) => Promise<Result<IAiAssistResult<TEntity>>>;\n /**\n * Execute an image-generation action: calls the provider's image API and\n * returns the generated images.\n * @returns Success with the image generation response, or failure.\n */\n readonly generateImages: (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ) => Promise<Result<AiAssist.IAiImageGenerationResponse>>;\n /**\n * List the models available from a provider, optionally filtered by capability.\n * @returns Success with the resolved model list, or failure (no silent fallback).\n */\n readonly listModels: (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ) => Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>>;\n /**\n * Stream a chat completion from the provider. The `onEvent` callback fires\n * for every event (text deltas, tool progress, errors); the returned\n * promise resolves with the aggregated final text and truncation flag on\n * success, or with `Result.fail` when the stream couldn't be started or\n * ended in a terminal error event.\n */\n readonly streamDirect: (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ) => Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>>;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.\n *\n * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),\n * returns the configured key if one happens to be set in the keystore, or\n * an empty string otherwise. ts-extras omits the `Authorization` header\n * entirely for empty keys, which self-hosted servers expect.\n * - When `descriptor.needsSecret !== false`, requires `secretName`, an\n * unlocked keystore, and a successful key fetch.\n *\n * @internal\n */\nfunction resolveApiKey(\n descriptor: AiAssist.IAiProviderDescriptor,\n providerConfig: AiAssist.IAiAssistProviderConfig,\n keyStore: AiAssist.IAiAssistKeyStore | undefined\n): Result<string> {\n const { provider, secretName } = providerConfig;\n if (descriptor.needsSecret === false) {\n if (!secretName || !keyStore?.isUnlocked) {\n return succeed('');\n }\n return succeed(keyStore.getApiKey(secretName).orDefault('') ?? '');\n }\n if (!secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);\n}\n\n/**\n * Checks whether a parsed AI response is an error object (with an \"error\" field)\n * rather than a valid entity. AI prompts instruct the model to return\n * `{ \"error\": \"...\", \"term\": \"...\" }` when it cannot confidently generate the entity.\n *\n * @param parsed - The parsed JSON from the AI response\n * @returns A failure Result with the error message if it's an error object, or undefined if not\n */\nexport function checkForAiErrorObject(parsed: unknown): Result<never> | undefined {\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as Record<string, unknown>).error === 'string'\n ) {\n const errorObj = parsed as Record<string, unknown>;\n const term = typeof errorObj.term === 'string' ? ` (term: \"${errorObj.term}\")` : '';\n return fail(`AI declined to generate${term}: ${errorObj.error}`);\n }\n return undefined;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Generic hook providing AI assist actions based on supplied settings and keystore.\n * @param params - Settings and keystore to use (typically from a workspace or app context)\n * @returns Available actions and execution functions\n * @public\n */\nexport function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult {\n const { settings, keyStore, logger } = params;\n const [isWorking, setIsWorking] = useState(false);\n\n const actions = useMemo((): ReadonlyArray<IAiAssistAction> => {\n const providers = settings?.providers ?? [{ provider: 'copy-paste' as AiAssist.AiProviderId }];\n const enabledSet = new Set(providers.map((p) => p.provider));\n const defaultProvider: AiAssist.AiProviderId =\n settings?.defaultProvider && enabledSet.has(settings.defaultProvider)\n ? settings.defaultProvider\n : providers[0]?.provider ?? ('copy-paste' as AiAssist.AiProviderId);\n\n return providers.map((config) => {\n let isAvailable = true;\n let unavailableReason: string | undefined;\n\n const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();\n\n // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip\n // the keystore check entirely — they're available as long as the\n // descriptor exists. Providers with `needsSecret: true` still require\n // a configured secretName and an unlocked keystore.\n if (config.provider !== 'copy-paste' && descriptor?.needsSecret !== false) {\n if (!config.secretName) {\n isAvailable = false;\n unavailableReason = 'No API key secret configured';\n } else if (!keyStore) {\n isAvailable = false;\n unavailableReason = 'No keystore available';\n } else if (!keyStore.isUnlocked) {\n isAvailable = false;\n unavailableReason = 'Keystore is locked';\n } else {\n const hasSecret = keyStore.hasSecret(config.secretName);\n if (hasSecret.isFailure() || !hasSecret.value) {\n isAvailable = false;\n unavailableReason = `API key \"${config.secretName}\" not found in keystore`;\n }\n }\n }\n\n const label = descriptor?.buttonLabel ?? config.provider;\n return {\n provider: config.provider,\n label,\n isDefault: config.provider === defaultProvider,\n isAvailable,\n unavailableReason\n };\n });\n }, [settings, keyStore]);\n\n const copyPrompt = useCallback(async (prompt: AiAssist.AiPrompt): Promise<Result<'copied'>> => {\n try {\n await navigator.clipboard.writeText(prompt.combined);\n return succeed('copied' as const);\n } catch {\n return fail('Failed to copy prompt to clipboard');\n }\n }, []);\n\n const generateDirect = useCallback(\n async <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ): Promise<Result<IAiAssistResult<TEntity>>> => {\n // Find the provider config and descriptor\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const maxAttempts = 3;\n const correctionMessages: Array<AiAssist.IChatMessage> = [];\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // Resolve effective tools: per-call override > settings defaults > none\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);\n\n // Call the API — through proxy if configured, otherwise direct\n const completionParams: AiAssist.IProviderCompletionParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n prompt,\n additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const responseResult = useProxy\n ? await AiAssist.callProxiedCompletion(settings!.proxyUrl!, completionParams)\n : await AiAssist.callProviderCompletion(completionParams);\n if (responseResult.isFailure()) {\n logger?.error(`AI completion failed: ${responseResult.message}`);\n return fail(responseResult.message);\n }\n\n const { content: rawResponse, truncated } = responseResult.value;\n logger?.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);\n\n // Truncated responses are almost certainly malformed JSON — fail early\n if (truncated) {\n logger?.warn('AI response truncated due to token limits');\n return fail('AI response was truncated due to token limits — try a shorter prompt');\n }\n\n // Strip markdown code fences and parse JSON\n const stripped = rawResponse\n .trim()\n .replace(/^```(?:\\w+)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/, '$1')\n .trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripped);\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n // JSON parse failures are not retryable — the model isn't producing valid JSON at all\n return fail(`AI returned invalid JSON: ${detail}`);\n }\n\n // Check for AI error object (model declined to generate)\n const aiError = checkForAiErrorObject(parsed);\n if (aiError !== undefined) {\n return aiError;\n }\n\n // Validate with the provided converter\n const entityResult = convert(parsed);\n if (entityResult.isSuccess()) {\n return succeed({ entity: entityResult.value, source: 'ai' as const });\n }\n\n // Validation failed — if we have retries left, send a correction\n logger?.warn(\n `AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`\n );\n if (attempt < maxAttempts - 1) {\n correctionMessages.push(\n { role: 'assistant', content: rawResponse },\n {\n role: 'user',\n content: `The JSON you returned failed validation with the following error:\\n\\n${entityResult.message}\\n\\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`\n }\n );\n } else {\n // Out of retries\n return fail(\n `AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`\n );\n }\n }\n\n /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */\n return fail('AI generation failed');\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const generateImages = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ): Promise<Result<AiAssist.IAiImageGenerationResponse>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n if (!AiAssist.supportsImageGeneration(descriptor)) {\n return fail(`Provider \"${provider}\" does not support image generation`);\n }\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const requestParams: AiAssist.IProviderImageGenerationParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n params,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedImageGeneration(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderImageGeneration(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI image generation failed: ${result.message}`);\n }\n return result;\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const listModels = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ): Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedListModels(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderListModels(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI list-models failed: ${result.message}`);\n }\n return result;\n },\n [settings, keyStore, logger]\n );\n\n const streamDirect = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ): Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options?.tools);\n const requestParams: AiAssist.IProviderCompletionStreamParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n prompt,\n messagesBefore: options?.messagesBefore,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined,\n signal: options?.signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);\n const openResult = useProxy\n ? await AiAssist.callProxiedCompletionStream(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderCompletionStream(requestParams);\n if (openResult.isFailure()) {\n logger?.error(`AI streaming failed to start: ${openResult.message}`);\n return fail(openResult.message);\n }\n\n setIsWorking(true);\n let fullText = '';\n let truncated = false;\n let terminalError: string | undefined;\n let receivedTerminalEvent = false;\n try {\n for await (const event of openResult.value) {\n try {\n onEvent(event);\n } catch (err: unknown) {\n // Consumer callback threw (e.g. setState-after-unmount). Convert\n // into a terminal failure so streamDirect always resolves to a\n // Result instead of rejecting and breaking its contract.\n terminalError = `AI stream event handler failed: ${\n err instanceof Error ? err.message : String(err)\n }`;\n receivedTerminalEvent = true;\n break;\n }\n if (event.type === 'text-delta') {\n fullText += event.delta;\n } else if (event.type === 'done') {\n fullText = event.fullText;\n truncated = event.truncated;\n receivedTerminalEvent = true;\n } else if (event.type === 'error') {\n terminalError = event.message;\n receivedTerminalEvent = true;\n }\n }\n } finally {\n setIsWorking(false);\n }\n if (terminalError !== undefined) {\n logger?.error(`AI streaming ended in error: ${terminalError}`);\n return fail(terminalError);\n }\n if (!receivedTerminalEvent) {\n const message = 'AI stream ended without a terminal done or error event';\n logger?.error(message);\n return fail(message);\n }\n return succeed({ fullText, truncated });\n },\n [settings, keyStore, logger]\n );\n\n return {\n actions,\n isWorking,\n copyPrompt,\n generateDirect,\n generateImages,\n listModels,\n streamDirect\n };\n}\n"]}
1
+ {"version":3,"file":"useAiAssist.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.\n *\n * Provides a list of available AI assist actions and functions for copy-paste\n * and direct API generation flows.\n *\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { AiAssist } from '@fgv/ts-extras';\nimport { fail, type Logging, Result, succeed } from '@fgv/ts-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Parameters for the generic useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistParams {\n /** Resolved AI assist settings (provider list + default). */\n readonly settings: AiAssist.IAiAssistSettings | undefined;\n /** Keystore for API key resolution (or undefined if no keystore). */\n readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * A single available AI assist action.\n * @public\n */\nexport interface IAiAssistAction {\n /** The provider identifier */\n readonly provider: AiAssist.AiProviderId;\n /** Display label (e.g. \"AI Assist | Copy\", \"AI Assist | Grok\") */\n readonly label: string;\n /** Whether this is the default (first) action */\n readonly isDefault: boolean;\n /** Whether the provider is currently available (keystore unlocked, secret present) */\n readonly isAvailable: boolean;\n /** Reason the provider is unavailable, if any */\n readonly unavailableReason?: string;\n}\n\n/**\n * Result of executing an AI assist action.\n * @public\n */\nexport interface IAiAssistResult<TEntity> {\n /** The generated entity */\n readonly entity: TEntity;\n /** Source indicator */\n readonly source: 'ai';\n}\n\n/**\n * Return type of the useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistResult {\n /** Available actions based on settings + keystore state */\n readonly actions: ReadonlyArray<IAiAssistAction>;\n /** Whether a direct assist call is in progress */\n readonly isWorking: boolean;\n /**\n * Execute a copy-paste action: copies the combined prompt to clipboard.\n * @returns Success with `'copied'`, or failure.\n */\n readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;\n /**\n * Execute a direct API action: calls the provider, validates the response, returns the entity.\n * @returns Success with the validated entity, or failure.\n */\n readonly generateDirect: <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ) => Promise<Result<IAiAssistResult<TEntity>>>;\n /**\n * Execute an image-generation action: calls the provider's image API and\n * returns the generated images.\n * @returns Success with the image generation response, or failure.\n */\n readonly generateImages: (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ) => Promise<Result<AiAssist.IAiImageGenerationResponse>>;\n /**\n * List the models available from a provider, optionally filtered by capability.\n * @returns Success with the resolved model list, or failure (no silent fallback).\n */\n readonly listModels: (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ) => Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>>;\n /**\n * Stream a chat completion from the provider. The `onEvent` callback fires\n * for every event (text deltas, tool progress, errors); the returned\n * promise resolves with the aggregated final text and truncation flag on\n * success, or with `Result.fail` when the stream couldn't be started or\n * ended in a terminal error event.\n */\n readonly streamDirect: (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ) => Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>>;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.\n *\n * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),\n * returns the configured key if one happens to be set in the keystore, or\n * an empty string otherwise. ts-extras omits the `Authorization` header\n * entirely for empty keys, which self-hosted servers expect.\n * - When `descriptor.needsSecret !== false`, requires `secretName`, an\n * unlocked keystore, and a successful key fetch.\n *\n * @internal\n */\nfunction resolveApiKey(\n descriptor: AiAssist.IAiProviderDescriptor,\n providerConfig: AiAssist.IAiAssistProviderConfig,\n keyStore: AiAssist.IAiAssistKeyStore | undefined\n): Result<string> {\n const { provider, secretName } = providerConfig;\n if (descriptor.needsSecret === false) {\n if (!secretName || !keyStore?.isUnlocked) {\n return succeed('');\n }\n return succeed(keyStore.getApiKey(secretName).orDefault('') ?? '');\n }\n if (!secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);\n}\n\n/**\n * Checks whether a parsed AI response is an error object (with an \"error\" field)\n * rather than a valid entity. AI prompts instruct the model to return\n * `{ \"error\": \"...\", \"term\": \"...\" }` when it cannot confidently generate the entity.\n *\n * @param parsed - The parsed JSON from the AI response\n * @returns A failure Result with the error message if it's an error object, or undefined if not\n */\nexport function checkForAiErrorObject(parsed: unknown): Result<never> | undefined {\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as Record<string, unknown>).error === 'string'\n ) {\n const errorObj = parsed as Record<string, unknown>;\n const term = typeof errorObj.term === 'string' ? ` (term: \"${errorObj.term}\")` : '';\n return fail(`AI declined to generate${term}: ${errorObj.error}`);\n }\n return undefined;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Generic hook providing AI assist actions based on supplied settings and keystore.\n * @param params - Settings and keystore to use (typically from a workspace or app context)\n * @returns Available actions and execution functions\n * @public\n */\nexport function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult {\n const { settings, keyStore, logger } = params;\n const [isWorking, setIsWorking] = useState(false);\n\n const actions = useMemo((): ReadonlyArray<IAiAssistAction> => {\n const providers = settings?.providers ?? [{ provider: 'copy-paste' as AiAssist.AiProviderId }];\n const enabledSet = new Set(providers.map((p) => p.provider));\n const defaultProvider: AiAssist.AiProviderId =\n settings?.defaultProvider && enabledSet.has(settings.defaultProvider)\n ? settings.defaultProvider\n : providers[0]?.provider ?? ('copy-paste' as AiAssist.AiProviderId);\n\n return providers.map((config) => {\n let isAvailable = true;\n let unavailableReason: string | undefined;\n\n const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();\n\n // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip\n // the keystore check entirely — they're available as long as the\n // descriptor exists. Providers with `needsSecret: true` still require\n // a configured secretName and an unlocked keystore.\n if (config.provider !== 'copy-paste' && descriptor?.needsSecret !== false) {\n if (!config.secretName) {\n isAvailable = false;\n unavailableReason = 'No API key secret configured';\n } else if (!keyStore) {\n isAvailable = false;\n unavailableReason = 'No keystore available';\n } else if (!keyStore.isUnlocked) {\n isAvailable = false;\n unavailableReason = 'Keystore is locked';\n } else {\n const hasSecret = keyStore.hasSecret(config.secretName);\n if (hasSecret.isFailure() || !hasSecret.value) {\n isAvailable = false;\n unavailableReason = `API key \"${config.secretName}\" not found in keystore`;\n }\n }\n }\n\n const label = descriptor?.buttonLabel ?? config.provider;\n return {\n provider: config.provider,\n label,\n isDefault: config.provider === defaultProvider,\n isAvailable,\n unavailableReason\n };\n });\n }, [settings, keyStore]);\n\n const copyPrompt = useCallback(async (prompt: AiAssist.AiPrompt): Promise<Result<'copied'>> => {\n try {\n await navigator.clipboard.writeText(prompt.combined);\n return succeed('copied' as const);\n } catch {\n return fail('Failed to copy prompt to clipboard');\n }\n }, []);\n\n const generateDirect = useCallback(\n async <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ): Promise<Result<IAiAssistResult<TEntity>>> => {\n // Find the provider config and descriptor\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const maxAttempts = 3;\n const correctionMessages: Array<AiAssist.IChatMessage> = [];\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // Resolve effective tools: per-call override > settings defaults > none\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);\n\n // Call the API — through proxy if configured, otherwise direct.\n // The unified request orders history before the current turn; the\n // correction-retry turns naturally append after the current user turn.\n const base = prompt.toRequest();\n const completionParams: AiAssist.IProviderCompletionParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...base.messages, ...correctionMessages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const responseResult = useProxy\n ? await AiAssist.callProxiedCompletion(settings!.proxyUrl!, completionParams)\n : await AiAssist.callProviderCompletion(completionParams);\n if (responseResult.isFailure()) {\n logger?.error(`AI completion failed: ${responseResult.message}`);\n return fail(responseResult.message);\n }\n\n const { content: rawResponse, truncated } = responseResult.value;\n logger?.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);\n\n // Truncated responses are almost certainly malformed JSON — fail early\n if (truncated) {\n logger?.warn('AI response truncated due to token limits');\n return fail('AI response was truncated due to token limits — try a shorter prompt');\n }\n\n // Strip markdown code fences and parse JSON\n const stripped = rawResponse\n .trim()\n .replace(/^```(?:\\w+)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/, '$1')\n .trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripped);\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n // JSON parse failures are not retryable — the model isn't producing valid JSON at all\n return fail(`AI returned invalid JSON: ${detail}`);\n }\n\n // Check for AI error object (model declined to generate)\n const aiError = checkForAiErrorObject(parsed);\n if (aiError !== undefined) {\n return aiError;\n }\n\n // Validate with the provided converter\n const entityResult = convert(parsed);\n if (entityResult.isSuccess()) {\n return succeed({ entity: entityResult.value, source: 'ai' as const });\n }\n\n // Validation failed — if we have retries left, send a correction\n logger?.warn(\n `AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`\n );\n if (attempt < maxAttempts - 1) {\n correctionMessages.push(\n { role: 'assistant', content: rawResponse },\n {\n role: 'user',\n content: `The JSON you returned failed validation with the following error:\\n\\n${entityResult.message}\\n\\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`\n }\n );\n } else {\n // Out of retries\n return fail(\n `AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`\n );\n }\n }\n\n /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */\n return fail('AI generation failed');\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const generateImages = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ): Promise<Result<AiAssist.IAiImageGenerationResponse>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n if (!AiAssist.supportsImageGeneration(descriptor)) {\n return fail(`Provider \"${provider}\" does not support image generation`);\n }\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const requestParams: AiAssist.IProviderImageGenerationParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n params,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedImageGeneration(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderImageGeneration(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI image generation failed: ${result.message}`);\n }\n return result;\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const listModels = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ): Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedListModels(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderListModels(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI list-models failed: ${result.message}`);\n }\n return result;\n },\n [settings, keyStore, logger]\n );\n\n const streamDirect = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ): Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options?.tools);\n // Order prior history before the current user turn (the unified request shape).\n const base = prompt.toRequest();\n const requestParams: AiAssist.IProviderCompletionStreamParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...(options?.messagesBefore ?? []), ...base.messages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined,\n signal: options?.signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);\n const openResult = useProxy\n ? await AiAssist.callProxiedCompletionStream(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderCompletionStream(requestParams);\n if (openResult.isFailure()) {\n logger?.error(`AI streaming failed to start: ${openResult.message}`);\n return fail(openResult.message);\n }\n\n setIsWorking(true);\n let fullText = '';\n let truncated = false;\n let terminalError: string | undefined;\n let receivedTerminalEvent = false;\n try {\n for await (const event of openResult.value) {\n try {\n onEvent(event);\n } catch (err: unknown) {\n // Consumer callback threw (e.g. setState-after-unmount). Convert\n // into a terminal failure so streamDirect always resolves to a\n // Result instead of rejecting and breaking its contract.\n terminalError = `AI stream event handler failed: ${\n err instanceof Error ? err.message : String(err)\n }`;\n receivedTerminalEvent = true;\n break;\n }\n if (event.type === 'text-delta') {\n fullText += event.delta;\n } else if (event.type === 'done') {\n fullText = event.fullText;\n truncated = event.truncated;\n receivedTerminalEvent = true;\n } else if (event.type === 'error') {\n terminalError = event.message;\n receivedTerminalEvent = true;\n }\n }\n } finally {\n setIsWorking(false);\n }\n if (terminalError !== undefined) {\n logger?.error(`AI streaming ended in error: ${terminalError}`);\n return fail(terminalError);\n }\n if (!receivedTerminalEvent) {\n const message = 'AI stream ended without a terminal done or error event';\n logger?.error(message);\n return fail(message);\n }\n return succeed({ fullText, truncated });\n },\n [settings, keyStore, logger]\n );\n\n return {\n actions,\n isWorking,\n copyPrompt,\n generateDirect,\n generateImages,\n listModels,\n streamDirect\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"useAiAssist.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useAiAssist.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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"}
@@ -182,12 +182,15 @@ function useAiAssist(params) {
182
182
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
183
183
  // Resolve effective tools: per-call override > settings defaults > none
184
184
  const effectiveTools = ts_extras_1.AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);
185
- // Call the API — through proxy if configured, otherwise direct
185
+ // Call the API — through proxy if configured, otherwise direct.
186
+ // The unified request orders history before the current turn; the
187
+ // correction-retry turns naturally append after the current user turn.
188
+ const base = prompt.toRequest();
186
189
  const completionParams = {
187
190
  descriptor,
188
191
  apiKey: apiKeyResult.value,
189
- prompt,
190
- additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
192
+ system: base.system,
193
+ messages: [...base.messages, ...correctionMessages],
191
194
  modelOverride: providerConfig.model,
192
195
  endpoint: providerConfig.endpoint,
193
196
  logger,
@@ -320,6 +323,7 @@ function useAiAssist(params) {
320
323
  }, [settings, keyStore, logger]);
321
324
  const streamDirect = (0, react_1.useCallback)(async (provider, prompt, onEvent, options) => {
322
325
  var _a, e_1, _b, _c;
326
+ var _d;
323
327
  const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
324
328
  if (!providerConfig) {
325
329
  return (0, ts_utils_1.fail)(`Provider "${provider}" not configured`);
@@ -334,11 +338,13 @@ function useAiAssist(params) {
334
338
  return (0, ts_utils_1.fail)(apiKeyResult.message);
335
339
  }
336
340
  const effectiveTools = ts_extras_1.AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options === null || options === void 0 ? void 0 : options.tools);
341
+ // Order prior history before the current user turn (the unified request shape).
342
+ const base = prompt.toRequest();
337
343
  const requestParams = {
338
344
  descriptor,
339
345
  apiKey: apiKeyResult.value,
340
- prompt,
341
- messagesBefore: options === null || options === void 0 ? void 0 : options.messagesBefore,
346
+ system: base.system,
347
+ messages: [...((_d = options === null || options === void 0 ? void 0 : options.messagesBefore) !== null && _d !== void 0 ? _d : []), ...base.messages],
342
348
  modelOverride: providerConfig.model,
343
349
  endpoint: providerConfig.endpoint,
344
350
  logger,
@@ -360,9 +366,9 @@ function useAiAssist(params) {
360
366
  let receivedTerminalEvent = false;
361
367
  try {
362
368
  try {
363
- for (var _d = true, _e = __asyncValues(openResult.value), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
364
- _c = _f.value;
365
- _d = false;
369
+ for (var _e = true, _f = __asyncValues(openResult.value), _g; _g = await _f.next(), _a = _g.done, !_a; _e = true) {
370
+ _c = _g.value;
371
+ _e = false;
366
372
  const event = _c;
367
373
  try {
368
374
  onEvent(event);
@@ -392,7 +398,7 @@ function useAiAssist(params) {
392
398
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
393
399
  finally {
394
400
  try {
395
- if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
401
+ if (!_e && !_a && (_b = _f.return)) await _b.call(_f);
396
402
  }
397
403
  finally { if (e_1) throw e_1.error; }
398
404
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useAiAssist.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.\n *\n * Provides a list of available AI assist actions and functions for copy-paste\n * and direct API generation flows.\n *\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { AiAssist } from '@fgv/ts-extras';\nimport { fail, type Logging, Result, succeed } from '@fgv/ts-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Parameters for the generic useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistParams {\n /** Resolved AI assist settings (provider list + default). */\n readonly settings: AiAssist.IAiAssistSettings | undefined;\n /** Keystore for API key resolution (or undefined if no keystore). */\n readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * A single available AI assist action.\n * @public\n */\nexport interface IAiAssistAction {\n /** The provider identifier */\n readonly provider: AiAssist.AiProviderId;\n /** Display label (e.g. \"AI Assist | Copy\", \"AI Assist | Grok\") */\n readonly label: string;\n /** Whether this is the default (first) action */\n readonly isDefault: boolean;\n /** Whether the provider is currently available (keystore unlocked, secret present) */\n readonly isAvailable: boolean;\n /** Reason the provider is unavailable, if any */\n readonly unavailableReason?: string;\n}\n\n/**\n * Result of executing an AI assist action.\n * @public\n */\nexport interface IAiAssistResult<TEntity> {\n /** The generated entity */\n readonly entity: TEntity;\n /** Source indicator */\n readonly source: 'ai';\n}\n\n/**\n * Return type of the useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistResult {\n /** Available actions based on settings + keystore state */\n readonly actions: ReadonlyArray<IAiAssistAction>;\n /** Whether a direct assist call is in progress */\n readonly isWorking: boolean;\n /**\n * Execute a copy-paste action: copies the combined prompt to clipboard.\n * @returns Success with `'copied'`, or failure.\n */\n readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;\n /**\n * Execute a direct API action: calls the provider, validates the response, returns the entity.\n * @returns Success with the validated entity, or failure.\n */\n readonly generateDirect: <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ) => Promise<Result<IAiAssistResult<TEntity>>>;\n /**\n * Execute an image-generation action: calls the provider's image API and\n * returns the generated images.\n * @returns Success with the image generation response, or failure.\n */\n readonly generateImages: (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ) => Promise<Result<AiAssist.IAiImageGenerationResponse>>;\n /**\n * List the models available from a provider, optionally filtered by capability.\n * @returns Success with the resolved model list, or failure (no silent fallback).\n */\n readonly listModels: (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ) => Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>>;\n /**\n * Stream a chat completion from the provider. The `onEvent` callback fires\n * for every event (text deltas, tool progress, errors); the returned\n * promise resolves with the aggregated final text and truncation flag on\n * success, or with `Result.fail` when the stream couldn't be started or\n * ended in a terminal error event.\n */\n readonly streamDirect: (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ) => Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>>;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.\n *\n * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),\n * returns the configured key if one happens to be set in the keystore, or\n * an empty string otherwise. ts-extras omits the `Authorization` header\n * entirely for empty keys, which self-hosted servers expect.\n * - When `descriptor.needsSecret !== false`, requires `secretName`, an\n * unlocked keystore, and a successful key fetch.\n *\n * @internal\n */\nfunction resolveApiKey(\n descriptor: AiAssist.IAiProviderDescriptor,\n providerConfig: AiAssist.IAiAssistProviderConfig,\n keyStore: AiAssist.IAiAssistKeyStore | undefined\n): Result<string> {\n const { provider, secretName } = providerConfig;\n if (descriptor.needsSecret === false) {\n if (!secretName || !keyStore?.isUnlocked) {\n return succeed('');\n }\n return succeed(keyStore.getApiKey(secretName).orDefault('') ?? '');\n }\n if (!secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);\n}\n\n/**\n * Checks whether a parsed AI response is an error object (with an \"error\" field)\n * rather than a valid entity. AI prompts instruct the model to return\n * `{ \"error\": \"...\", \"term\": \"...\" }` when it cannot confidently generate the entity.\n *\n * @param parsed - The parsed JSON from the AI response\n * @returns A failure Result with the error message if it's an error object, or undefined if not\n */\nexport function checkForAiErrorObject(parsed: unknown): Result<never> | undefined {\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as Record<string, unknown>).error === 'string'\n ) {\n const errorObj = parsed as Record<string, unknown>;\n const term = typeof errorObj.term === 'string' ? ` (term: \"${errorObj.term}\")` : '';\n return fail(`AI declined to generate${term}: ${errorObj.error}`);\n }\n return undefined;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Generic hook providing AI assist actions based on supplied settings and keystore.\n * @param params - Settings and keystore to use (typically from a workspace or app context)\n * @returns Available actions and execution functions\n * @public\n */\nexport function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult {\n const { settings, keyStore, logger } = params;\n const [isWorking, setIsWorking] = useState(false);\n\n const actions = useMemo((): ReadonlyArray<IAiAssistAction> => {\n const providers = settings?.providers ?? [{ provider: 'copy-paste' as AiAssist.AiProviderId }];\n const enabledSet = new Set(providers.map((p) => p.provider));\n const defaultProvider: AiAssist.AiProviderId =\n settings?.defaultProvider && enabledSet.has(settings.defaultProvider)\n ? settings.defaultProvider\n : providers[0]?.provider ?? ('copy-paste' as AiAssist.AiProviderId);\n\n return providers.map((config) => {\n let isAvailable = true;\n let unavailableReason: string | undefined;\n\n const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();\n\n // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip\n // the keystore check entirely — they're available as long as the\n // descriptor exists. Providers with `needsSecret: true` still require\n // a configured secretName and an unlocked keystore.\n if (config.provider !== 'copy-paste' && descriptor?.needsSecret !== false) {\n if (!config.secretName) {\n isAvailable = false;\n unavailableReason = 'No API key secret configured';\n } else if (!keyStore) {\n isAvailable = false;\n unavailableReason = 'No keystore available';\n } else if (!keyStore.isUnlocked) {\n isAvailable = false;\n unavailableReason = 'Keystore is locked';\n } else {\n const hasSecret = keyStore.hasSecret(config.secretName);\n if (hasSecret.isFailure() || !hasSecret.value) {\n isAvailable = false;\n unavailableReason = `API key \"${config.secretName}\" not found in keystore`;\n }\n }\n }\n\n const label = descriptor?.buttonLabel ?? config.provider;\n return {\n provider: config.provider,\n label,\n isDefault: config.provider === defaultProvider,\n isAvailable,\n unavailableReason\n };\n });\n }, [settings, keyStore]);\n\n const copyPrompt = useCallback(async (prompt: AiAssist.AiPrompt): Promise<Result<'copied'>> => {\n try {\n await navigator.clipboard.writeText(prompt.combined);\n return succeed('copied' as const);\n } catch {\n return fail('Failed to copy prompt to clipboard');\n }\n }, []);\n\n const generateDirect = useCallback(\n async <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ): Promise<Result<IAiAssistResult<TEntity>>> => {\n // Find the provider config and descriptor\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const maxAttempts = 3;\n const correctionMessages: Array<AiAssist.IChatMessage> = [];\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // Resolve effective tools: per-call override > settings defaults > none\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);\n\n // Call the API — through proxy if configured, otherwise direct\n const completionParams: AiAssist.IProviderCompletionParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n prompt,\n additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const responseResult = useProxy\n ? await AiAssist.callProxiedCompletion(settings!.proxyUrl!, completionParams)\n : await AiAssist.callProviderCompletion(completionParams);\n if (responseResult.isFailure()) {\n logger?.error(`AI completion failed: ${responseResult.message}`);\n return fail(responseResult.message);\n }\n\n const { content: rawResponse, truncated } = responseResult.value;\n logger?.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);\n\n // Truncated responses are almost certainly malformed JSON — fail early\n if (truncated) {\n logger?.warn('AI response truncated due to token limits');\n return fail('AI response was truncated due to token limits — try a shorter prompt');\n }\n\n // Strip markdown code fences and parse JSON\n const stripped = rawResponse\n .trim()\n .replace(/^```(?:\\w+)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/, '$1')\n .trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripped);\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n // JSON parse failures are not retryable — the model isn't producing valid JSON at all\n return fail(`AI returned invalid JSON: ${detail}`);\n }\n\n // Check for AI error object (model declined to generate)\n const aiError = checkForAiErrorObject(parsed);\n if (aiError !== undefined) {\n return aiError;\n }\n\n // Validate with the provided converter\n const entityResult = convert(parsed);\n if (entityResult.isSuccess()) {\n return succeed({ entity: entityResult.value, source: 'ai' as const });\n }\n\n // Validation failed — if we have retries left, send a correction\n logger?.warn(\n `AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`\n );\n if (attempt < maxAttempts - 1) {\n correctionMessages.push(\n { role: 'assistant', content: rawResponse },\n {\n role: 'user',\n content: `The JSON you returned failed validation with the following error:\\n\\n${entityResult.message}\\n\\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`\n }\n );\n } else {\n // Out of retries\n return fail(\n `AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`\n );\n }\n }\n\n /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */\n return fail('AI generation failed');\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const generateImages = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ): Promise<Result<AiAssist.IAiImageGenerationResponse>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n if (!AiAssist.supportsImageGeneration(descriptor)) {\n return fail(`Provider \"${provider}\" does not support image generation`);\n }\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const requestParams: AiAssist.IProviderImageGenerationParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n params,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedImageGeneration(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderImageGeneration(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI image generation failed: ${result.message}`);\n }\n return result;\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const listModels = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ): Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedListModels(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderListModels(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI list-models failed: ${result.message}`);\n }\n return result;\n },\n [settings, keyStore, logger]\n );\n\n const streamDirect = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ): Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options?.tools);\n const requestParams: AiAssist.IProviderCompletionStreamParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n prompt,\n messagesBefore: options?.messagesBefore,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined,\n signal: options?.signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);\n const openResult = useProxy\n ? await AiAssist.callProxiedCompletionStream(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderCompletionStream(requestParams);\n if (openResult.isFailure()) {\n logger?.error(`AI streaming failed to start: ${openResult.message}`);\n return fail(openResult.message);\n }\n\n setIsWorking(true);\n let fullText = '';\n let truncated = false;\n let terminalError: string | undefined;\n let receivedTerminalEvent = false;\n try {\n for await (const event of openResult.value) {\n try {\n onEvent(event);\n } catch (err: unknown) {\n // Consumer callback threw (e.g. setState-after-unmount). Convert\n // into a terminal failure so streamDirect always resolves to a\n // Result instead of rejecting and breaking its contract.\n terminalError = `AI stream event handler failed: ${\n err instanceof Error ? err.message : String(err)\n }`;\n receivedTerminalEvent = true;\n break;\n }\n if (event.type === 'text-delta') {\n fullText += event.delta;\n } else if (event.type === 'done') {\n fullText = event.fullText;\n truncated = event.truncated;\n receivedTerminalEvent = true;\n } else if (event.type === 'error') {\n terminalError = event.message;\n receivedTerminalEvent = true;\n }\n }\n } finally {\n setIsWorking(false);\n }\n if (terminalError !== undefined) {\n logger?.error(`AI streaming ended in error: ${terminalError}`);\n return fail(terminalError);\n }\n if (!receivedTerminalEvent) {\n const message = 'AI stream ended without a terminal done or error event';\n logger?.error(message);\n return fail(message);\n }\n return succeed({ fullText, truncated });\n },\n [settings, keyStore, logger]\n );\n\n return {\n actions,\n isWorking,\n copyPrompt,\n generateDirect,\n generateImages,\n listModels,\n streamDirect\n };\n}\n"]}
1
+ {"version":3,"file":"useAiAssist.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.\n *\n * Provides a list of available AI assist actions and functions for copy-paste\n * and direct API generation flows.\n *\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { AiAssist } from '@fgv/ts-extras';\nimport { fail, type Logging, Result, succeed } from '@fgv/ts-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Parameters for the generic useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistParams {\n /** Resolved AI assist settings (provider list + default). */\n readonly settings: AiAssist.IAiAssistSettings | undefined;\n /** Keystore for API key resolution (or undefined if no keystore). */\n readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * A single available AI assist action.\n * @public\n */\nexport interface IAiAssistAction {\n /** The provider identifier */\n readonly provider: AiAssist.AiProviderId;\n /** Display label (e.g. \"AI Assist | Copy\", \"AI Assist | Grok\") */\n readonly label: string;\n /** Whether this is the default (first) action */\n readonly isDefault: boolean;\n /** Whether the provider is currently available (keystore unlocked, secret present) */\n readonly isAvailable: boolean;\n /** Reason the provider is unavailable, if any */\n readonly unavailableReason?: string;\n}\n\n/**\n * Result of executing an AI assist action.\n * @public\n */\nexport interface IAiAssistResult<TEntity> {\n /** The generated entity */\n readonly entity: TEntity;\n /** Source indicator */\n readonly source: 'ai';\n}\n\n/**\n * Return type of the useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistResult {\n /** Available actions based on settings + keystore state */\n readonly actions: ReadonlyArray<IAiAssistAction>;\n /** Whether a direct assist call is in progress */\n readonly isWorking: boolean;\n /**\n * Execute a copy-paste action: copies the combined prompt to clipboard.\n * @returns Success with `'copied'`, or failure.\n */\n readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;\n /**\n * Execute a direct API action: calls the provider, validates the response, returns the entity.\n * @returns Success with the validated entity, or failure.\n */\n readonly generateDirect: <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ) => Promise<Result<IAiAssistResult<TEntity>>>;\n /**\n * Execute an image-generation action: calls the provider's image API and\n * returns the generated images.\n * @returns Success with the image generation response, or failure.\n */\n readonly generateImages: (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ) => Promise<Result<AiAssist.IAiImageGenerationResponse>>;\n /**\n * List the models available from a provider, optionally filtered by capability.\n * @returns Success with the resolved model list, or failure (no silent fallback).\n */\n readonly listModels: (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ) => Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>>;\n /**\n * Stream a chat completion from the provider. The `onEvent` callback fires\n * for every event (text deltas, tool progress, errors); the returned\n * promise resolves with the aggregated final text and truncation flag on\n * success, or with `Result.fail` when the stream couldn't be started or\n * ended in a terminal error event.\n */\n readonly streamDirect: (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ) => Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>>;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.\n *\n * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),\n * returns the configured key if one happens to be set in the keystore, or\n * an empty string otherwise. ts-extras omits the `Authorization` header\n * entirely for empty keys, which self-hosted servers expect.\n * - When `descriptor.needsSecret !== false`, requires `secretName`, an\n * unlocked keystore, and a successful key fetch.\n *\n * @internal\n */\nfunction resolveApiKey(\n descriptor: AiAssist.IAiProviderDescriptor,\n providerConfig: AiAssist.IAiAssistProviderConfig,\n keyStore: AiAssist.IAiAssistKeyStore | undefined\n): Result<string> {\n const { provider, secretName } = providerConfig;\n if (descriptor.needsSecret === false) {\n if (!secretName || !keyStore?.isUnlocked) {\n return succeed('');\n }\n return succeed(keyStore.getApiKey(secretName).orDefault('') ?? '');\n }\n if (!secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);\n}\n\n/**\n * Checks whether a parsed AI response is an error object (with an \"error\" field)\n * rather than a valid entity. AI prompts instruct the model to return\n * `{ \"error\": \"...\", \"term\": \"...\" }` when it cannot confidently generate the entity.\n *\n * @param parsed - The parsed JSON from the AI response\n * @returns A failure Result with the error message if it's an error object, or undefined if not\n */\nexport function checkForAiErrorObject(parsed: unknown): Result<never> | undefined {\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as Record<string, unknown>).error === 'string'\n ) {\n const errorObj = parsed as Record<string, unknown>;\n const term = typeof errorObj.term === 'string' ? ` (term: \"${errorObj.term}\")` : '';\n return fail(`AI declined to generate${term}: ${errorObj.error}`);\n }\n return undefined;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Generic hook providing AI assist actions based on supplied settings and keystore.\n * @param params - Settings and keystore to use (typically from a workspace or app context)\n * @returns Available actions and execution functions\n * @public\n */\nexport function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult {\n const { settings, keyStore, logger } = params;\n const [isWorking, setIsWorking] = useState(false);\n\n const actions = useMemo((): ReadonlyArray<IAiAssistAction> => {\n const providers = settings?.providers ?? [{ provider: 'copy-paste' as AiAssist.AiProviderId }];\n const enabledSet = new Set(providers.map((p) => p.provider));\n const defaultProvider: AiAssist.AiProviderId =\n settings?.defaultProvider && enabledSet.has(settings.defaultProvider)\n ? settings.defaultProvider\n : providers[0]?.provider ?? ('copy-paste' as AiAssist.AiProviderId);\n\n return providers.map((config) => {\n let isAvailable = true;\n let unavailableReason: string | undefined;\n\n const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();\n\n // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip\n // the keystore check entirely — they're available as long as the\n // descriptor exists. Providers with `needsSecret: true` still require\n // a configured secretName and an unlocked keystore.\n if (config.provider !== 'copy-paste' && descriptor?.needsSecret !== false) {\n if (!config.secretName) {\n isAvailable = false;\n unavailableReason = 'No API key secret configured';\n } else if (!keyStore) {\n isAvailable = false;\n unavailableReason = 'No keystore available';\n } else if (!keyStore.isUnlocked) {\n isAvailable = false;\n unavailableReason = 'Keystore is locked';\n } else {\n const hasSecret = keyStore.hasSecret(config.secretName);\n if (hasSecret.isFailure() || !hasSecret.value) {\n isAvailable = false;\n unavailableReason = `API key \"${config.secretName}\" not found in keystore`;\n }\n }\n }\n\n const label = descriptor?.buttonLabel ?? config.provider;\n return {\n provider: config.provider,\n label,\n isDefault: config.provider === defaultProvider,\n isAvailable,\n unavailableReason\n };\n });\n }, [settings, keyStore]);\n\n const copyPrompt = useCallback(async (prompt: AiAssist.AiPrompt): Promise<Result<'copied'>> => {\n try {\n await navigator.clipboard.writeText(prompt.combined);\n return succeed('copied' as const);\n } catch {\n return fail('Failed to copy prompt to clipboard');\n }\n }, []);\n\n const generateDirect = useCallback(\n async <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ): Promise<Result<IAiAssistResult<TEntity>>> => {\n // Find the provider config and descriptor\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const maxAttempts = 3;\n const correctionMessages: Array<AiAssist.IChatMessage> = [];\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // Resolve effective tools: per-call override > settings defaults > none\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);\n\n // Call the API — through proxy if configured, otherwise direct.\n // The unified request orders history before the current turn; the\n // correction-retry turns naturally append after the current user turn.\n const base = prompt.toRequest();\n const completionParams: AiAssist.IProviderCompletionParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...base.messages, ...correctionMessages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const responseResult = useProxy\n ? await AiAssist.callProxiedCompletion(settings!.proxyUrl!, completionParams)\n : await AiAssist.callProviderCompletion(completionParams);\n if (responseResult.isFailure()) {\n logger?.error(`AI completion failed: ${responseResult.message}`);\n return fail(responseResult.message);\n }\n\n const { content: rawResponse, truncated } = responseResult.value;\n logger?.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);\n\n // Truncated responses are almost certainly malformed JSON — fail early\n if (truncated) {\n logger?.warn('AI response truncated due to token limits');\n return fail('AI response was truncated due to token limits — try a shorter prompt');\n }\n\n // Strip markdown code fences and parse JSON\n const stripped = rawResponse\n .trim()\n .replace(/^```(?:\\w+)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/, '$1')\n .trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripped);\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n // JSON parse failures are not retryable — the model isn't producing valid JSON at all\n return fail(`AI returned invalid JSON: ${detail}`);\n }\n\n // Check for AI error object (model declined to generate)\n const aiError = checkForAiErrorObject(parsed);\n if (aiError !== undefined) {\n return aiError;\n }\n\n // Validate with the provided converter\n const entityResult = convert(parsed);\n if (entityResult.isSuccess()) {\n return succeed({ entity: entityResult.value, source: 'ai' as const });\n }\n\n // Validation failed — if we have retries left, send a correction\n logger?.warn(\n `AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`\n );\n if (attempt < maxAttempts - 1) {\n correctionMessages.push(\n { role: 'assistant', content: rawResponse },\n {\n role: 'user',\n content: `The JSON you returned failed validation with the following error:\\n\\n${entityResult.message}\\n\\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`\n }\n );\n } else {\n // Out of retries\n return fail(\n `AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`\n );\n }\n }\n\n /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */\n return fail('AI generation failed');\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const generateImages = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ): Promise<Result<AiAssist.IAiImageGenerationResponse>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n if (!AiAssist.supportsImageGeneration(descriptor)) {\n return fail(`Provider \"${provider}\" does not support image generation`);\n }\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const requestParams: AiAssist.IProviderImageGenerationParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n params,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedImageGeneration(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderImageGeneration(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI image generation failed: ${result.message}`);\n }\n return result;\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const listModels = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ): Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedListModels(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderListModels(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI list-models failed: ${result.message}`);\n }\n return result;\n },\n [settings, keyStore, logger]\n );\n\n const streamDirect = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ): Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options?.tools);\n // Order prior history before the current user turn (the unified request shape).\n const base = prompt.toRequest();\n const requestParams: AiAssist.IProviderCompletionStreamParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...(options?.messagesBefore ?? []), ...base.messages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined,\n signal: options?.signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);\n const openResult = useProxy\n ? await AiAssist.callProxiedCompletionStream(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderCompletionStream(requestParams);\n if (openResult.isFailure()) {\n logger?.error(`AI streaming failed to start: ${openResult.message}`);\n return fail(openResult.message);\n }\n\n setIsWorking(true);\n let fullText = '';\n let truncated = false;\n let terminalError: string | undefined;\n let receivedTerminalEvent = false;\n try {\n for await (const event of openResult.value) {\n try {\n onEvent(event);\n } catch (err: unknown) {\n // Consumer callback threw (e.g. setState-after-unmount). Convert\n // into a terminal failure so streamDirect always resolves to a\n // Result instead of rejecting and breaking its contract.\n terminalError = `AI stream event handler failed: ${\n err instanceof Error ? err.message : String(err)\n }`;\n receivedTerminalEvent = true;\n break;\n }\n if (event.type === 'text-delta') {\n fullText += event.delta;\n } else if (event.type === 'done') {\n fullText = event.fullText;\n truncated = event.truncated;\n receivedTerminalEvent = true;\n } else if (event.type === 'error') {\n terminalError = event.message;\n receivedTerminalEvent = true;\n }\n }\n } finally {\n setIsWorking(false);\n }\n if (terminalError !== undefined) {\n logger?.error(`AI streaming ended in error: ${terminalError}`);\n return fail(terminalError);\n }\n if (!receivedTerminalEvent) {\n const message = 'AI stream ended without a terminal done or error event';\n logger?.error(message);\n return fail(message);\n }\n return succeed({ fullText, truncated });\n },\n [settings, keyStore, logger]\n );\n\n return {\n actions,\n isWorking,\n copyPrompt,\n generateDirect,\n generateImages,\n listModels,\n streamDirect\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-app-shell",
3
- "version": "5.1.0-34",
3
+ "version": "5.1.0-35",
4
4
  "description": "Shared React UI primitives for application shells: column cascade, sidebar, toast/log messages, command palette, keybinding registry",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@heroicons/react": "~2.2.0",
21
21
  "tslib": "^2.8.1",
22
- "@fgv/ts-extras": "5.1.0-34",
23
- "@fgv/ts-utils": "5.1.0-34"
22
+ "@fgv/ts-utils": "5.1.0-35",
23
+ "@fgv/ts-extras": "5.1.0-35"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "react": ">=18.0.0",
@@ -53,8 +53,8 @@
53
53
  "@rushstack/heft-jest-plugin": "1.2.6",
54
54
  "@testing-library/dom": "^10.4.0",
55
55
  "@rushstack/heft-node-rig": "2.11.27",
56
- "@fgv/ts-utils-jest": "5.1.0-34",
57
- "@fgv/heft-dual-rig": "5.1.0-34"
56
+ "@fgv/heft-dual-rig": "5.1.0-35",
57
+ "@fgv/ts-utils-jest": "5.1.0-35"
58
58
  },
59
59
  "exports": {
60
60
  ".": {