@fgv/ts-app-shell 5.1.0-20 → 5.1.0-21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/ai-assist/useAiAssist.js +49 -38
- package/dist/packlets/ai-assist/useAiAssist.js.map +1 -1
- package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -1
- package/lib/packlets/ai-assist/useAiAssist.js +49 -38
- package/lib/packlets/ai-assist/useAiAssist.js.map +1 -1
- package/package.json +5 -5
|
@@ -40,6 +40,35 @@ import { fail, succeed } from '@fgv/ts-utils';
|
|
|
40
40
|
// ============================================================================
|
|
41
41
|
// Helpers
|
|
42
42
|
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.
|
|
45
|
+
*
|
|
46
|
+
* - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),
|
|
47
|
+
* returns the configured key if one happens to be set in the keystore, or
|
|
48
|
+
* an empty string otherwise. ts-extras omits the `Authorization` header
|
|
49
|
+
* entirely for empty keys, which self-hosted servers expect.
|
|
50
|
+
* - When `descriptor.needsSecret !== false`, requires `secretName`, an
|
|
51
|
+
* unlocked keystore, and a successful key fetch.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
function resolveApiKey(descriptor, providerConfig, keyStore) {
|
|
56
|
+
var _a;
|
|
57
|
+
const { provider, secretName } = providerConfig;
|
|
58
|
+
if (descriptor.needsSecret === false) {
|
|
59
|
+
if (!secretName || !(keyStore === null || keyStore === void 0 ? void 0 : keyStore.isUnlocked)) {
|
|
60
|
+
return succeed('');
|
|
61
|
+
}
|
|
62
|
+
return succeed((_a = keyStore.getApiKey(secretName).orDefault('')) !== null && _a !== void 0 ? _a : '');
|
|
63
|
+
}
|
|
64
|
+
if (!secretName) {
|
|
65
|
+
return fail(`Provider "${provider}" has no secret name configured`);
|
|
66
|
+
}
|
|
67
|
+
if (!keyStore) {
|
|
68
|
+
return fail('No keystore available');
|
|
69
|
+
}
|
|
70
|
+
return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);
|
|
71
|
+
}
|
|
43
72
|
/**
|
|
44
73
|
* Checks whether a parsed AI response is an error object (with an "error" field)
|
|
45
74
|
* rather than a valid entity. AI prompts instruct the model to return
|
|
@@ -79,11 +108,15 @@ export function useAiAssist(params) {
|
|
|
79
108
|
? settings.defaultProvider
|
|
80
109
|
: (_c = (_b = providers[0]) === null || _b === void 0 ? void 0 : _b.provider) !== null && _c !== void 0 ? _c : 'copy-paste';
|
|
81
110
|
return providers.map((config) => {
|
|
82
|
-
var _a
|
|
111
|
+
var _a;
|
|
83
112
|
let isAvailable = true;
|
|
84
113
|
let unavailableReason;
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();
|
|
115
|
+
// Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip
|
|
116
|
+
// the keystore check entirely — they're available as long as the
|
|
117
|
+
// descriptor exists. Providers with `needsSecret: true` still require
|
|
118
|
+
// a configured secretName and an unlocked keystore.
|
|
119
|
+
if (config.provider !== 'copy-paste' && (descriptor === null || descriptor === void 0 ? void 0 : descriptor.needsSecret) !== false) {
|
|
87
120
|
if (!config.secretName) {
|
|
88
121
|
isAvailable = false;
|
|
89
122
|
unavailableReason = 'No API key secret configured';
|
|
@@ -104,7 +137,7 @@ export function useAiAssist(params) {
|
|
|
104
137
|
}
|
|
105
138
|
}
|
|
106
139
|
}
|
|
107
|
-
const label = (
|
|
140
|
+
const label = (_a = descriptor === null || descriptor === void 0 ? void 0 : descriptor.buttonLabel) !== null && _a !== void 0 ? _a : config.provider;
|
|
108
141
|
return {
|
|
109
142
|
provider: config.provider,
|
|
110
143
|
label,
|
|
@@ -134,16 +167,9 @@ export function useAiAssist(params) {
|
|
|
134
167
|
return fail(descriptorResult.message);
|
|
135
168
|
}
|
|
136
169
|
const descriptor = descriptorResult.value;
|
|
137
|
-
|
|
138
|
-
return fail(`Provider "${provider}" has no secret name configured`);
|
|
139
|
-
}
|
|
140
|
-
if (!keyStore) {
|
|
141
|
-
return fail('No keystore available');
|
|
142
|
-
}
|
|
143
|
-
// Get API key from keystore
|
|
144
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
170
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
145
171
|
if (apiKeyResult.isFailure()) {
|
|
146
|
-
return fail(
|
|
172
|
+
return fail(apiKeyResult.message);
|
|
147
173
|
}
|
|
148
174
|
setIsWorking(true);
|
|
149
175
|
try {
|
|
@@ -159,6 +185,7 @@ export function useAiAssist(params) {
|
|
|
159
185
|
prompt,
|
|
160
186
|
additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
|
|
161
187
|
modelOverride: providerConfig.model,
|
|
188
|
+
endpoint: providerConfig.endpoint,
|
|
162
189
|
logger,
|
|
163
190
|
tools: effectiveTools.length > 0 ? effectiveTools : undefined
|
|
164
191
|
};
|
|
@@ -234,15 +261,9 @@ export function useAiAssist(params) {
|
|
|
234
261
|
if (!AiAssist.supportsImageGeneration(descriptor)) {
|
|
235
262
|
return fail(`Provider "${provider}" does not support image generation`);
|
|
236
263
|
}
|
|
237
|
-
|
|
238
|
-
return fail(`Provider "${provider}" has no secret name configured`);
|
|
239
|
-
}
|
|
240
|
-
if (!keyStore) {
|
|
241
|
-
return fail('No keystore available');
|
|
242
|
-
}
|
|
243
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
264
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
244
265
|
if (apiKeyResult.isFailure()) {
|
|
245
|
-
return fail(
|
|
266
|
+
return fail(apiKeyResult.message);
|
|
246
267
|
}
|
|
247
268
|
setIsWorking(true);
|
|
248
269
|
try {
|
|
@@ -251,6 +272,7 @@ export function useAiAssist(params) {
|
|
|
251
272
|
apiKey: apiKeyResult.value,
|
|
252
273
|
params,
|
|
253
274
|
modelOverride: providerConfig.model,
|
|
275
|
+
endpoint: providerConfig.endpoint,
|
|
254
276
|
logger,
|
|
255
277
|
signal
|
|
256
278
|
};
|
|
@@ -277,17 +299,11 @@ export function useAiAssist(params) {
|
|
|
277
299
|
return fail(descriptorResult.message);
|
|
278
300
|
}
|
|
279
301
|
const descriptor = descriptorResult.value;
|
|
280
|
-
|
|
281
|
-
return fail(`Provider "${provider}" has no secret name configured`);
|
|
282
|
-
}
|
|
283
|
-
if (!keyStore) {
|
|
284
|
-
return fail('No keystore available');
|
|
285
|
-
}
|
|
286
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
302
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
287
303
|
if (apiKeyResult.isFailure()) {
|
|
288
|
-
return fail(
|
|
304
|
+
return fail(apiKeyResult.message);
|
|
289
305
|
}
|
|
290
|
-
const requestParams = Object.assign(Object.assign({ descriptor, apiKey: apiKeyResult.value }, (capability !== undefined ? { capability } : {})), { logger,
|
|
306
|
+
const requestParams = Object.assign(Object.assign({ descriptor, apiKey: apiKeyResult.value }, (capability !== undefined ? { capability } : {})), { endpoint: providerConfig.endpoint, logger,
|
|
291
307
|
signal });
|
|
292
308
|
const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.corsRestricted);
|
|
293
309
|
const result = useProxy
|
|
@@ -309,15 +325,9 @@ export function useAiAssist(params) {
|
|
|
309
325
|
return fail(descriptorResult.message);
|
|
310
326
|
}
|
|
311
327
|
const descriptor = descriptorResult.value;
|
|
312
|
-
|
|
313
|
-
return fail(`Provider "${provider}" has no secret name configured`);
|
|
314
|
-
}
|
|
315
|
-
if (!keyStore) {
|
|
316
|
-
return fail('No keystore available');
|
|
317
|
-
}
|
|
318
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
328
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
319
329
|
if (apiKeyResult.isFailure()) {
|
|
320
|
-
return fail(
|
|
330
|
+
return fail(apiKeyResult.message);
|
|
321
331
|
}
|
|
322
332
|
const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options === null || options === void 0 ? void 0 : options.tools);
|
|
323
333
|
const requestParams = {
|
|
@@ -326,6 +336,7 @@ export function useAiAssist(params) {
|
|
|
326
336
|
prompt,
|
|
327
337
|
messagesBefore: options === null || options === void 0 ? void 0 : options.messagesBefore,
|
|
328
338
|
modelOverride: providerConfig.model,
|
|
339
|
+
endpoint: providerConfig.endpoint,
|
|
329
340
|
logger,
|
|
330
341
|
tools: effectiveTools.length > 0 ? effectiveTools : undefined,
|
|
331
342
|
signal: options === null || options === void 0 ? void 0 : options.signal
|
|
@@ -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 * 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 if (config.provider !== 'copy-paste') {\n // API-based providers need a secret name and unlocked keystore with that secret\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 =\n AiAssist.getProviderDescriptor(config.provider).orDefault()?.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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n // Get API key from keystore\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${apiKeyResult.message}`);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiAssist.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"AAiCA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AAMpE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAC1D,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAC1D,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC;IACzC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,OAAO;IACtC,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uBAAuB;IACvB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACjD,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAC/B,QAAQ,EAAE,QAAQ,CAAC,YAAY,EAC/B,MAAM,EAAE,QAAQ,CAAC,QAAQ,EACzB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,EAC3C,KAAK,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,KAC/C,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,CACvB,QAAQ,EAAE,QAAQ,CAAC,YAAY,EAC/B,MAAM,EAAE,QAAQ,CAAC,wBAAwB,EACzC,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC1D;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CACnB,QAAQ,EAAE,QAAQ,CAAC,YAAY,EAC/B,UAAU,CAAC,EAAE,QAAQ,CAAC,iBAAiB,EACvC,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE,CACrB,QAAQ,EAAE,QAAQ,CAAC,YAAY,EAC/B,MAAM,EAAE,QAAQ,CAAC,QAAQ,EACzB,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,KAAK,IAAI,EACjD,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAC5D,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;KAC/B,KACE,OAAO,CAAC,MAAM,CAAC;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAC;CAClF;
|
|
1
|
+
{"version":3,"file":"useAiAssist.d.ts","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,35 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
44
44
|
// ============================================================================
|
|
45
45
|
// Helpers
|
|
46
46
|
// ============================================================================
|
|
47
|
+
/**
|
|
48
|
+
* Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.
|
|
49
|
+
*
|
|
50
|
+
* - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),
|
|
51
|
+
* returns the configured key if one happens to be set in the keystore, or
|
|
52
|
+
* an empty string otherwise. ts-extras omits the `Authorization` header
|
|
53
|
+
* entirely for empty keys, which self-hosted servers expect.
|
|
54
|
+
* - When `descriptor.needsSecret !== false`, requires `secretName`, an
|
|
55
|
+
* unlocked keystore, and a successful key fetch.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
function resolveApiKey(descriptor, providerConfig, keyStore) {
|
|
60
|
+
var _a;
|
|
61
|
+
const { provider, secretName } = providerConfig;
|
|
62
|
+
if (descriptor.needsSecret === false) {
|
|
63
|
+
if (!secretName || !(keyStore === null || keyStore === void 0 ? void 0 : keyStore.isUnlocked)) {
|
|
64
|
+
return (0, ts_utils_1.succeed)('');
|
|
65
|
+
}
|
|
66
|
+
return (0, ts_utils_1.succeed)((_a = keyStore.getApiKey(secretName).orDefault('')) !== null && _a !== void 0 ? _a : '');
|
|
67
|
+
}
|
|
68
|
+
if (!secretName) {
|
|
69
|
+
return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
|
|
70
|
+
}
|
|
71
|
+
if (!keyStore) {
|
|
72
|
+
return (0, ts_utils_1.fail)('No keystore available');
|
|
73
|
+
}
|
|
74
|
+
return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);
|
|
75
|
+
}
|
|
47
76
|
/**
|
|
48
77
|
* Checks whether a parsed AI response is an error object (with an "error" field)
|
|
49
78
|
* rather than a valid entity. AI prompts instruct the model to return
|
|
@@ -83,11 +112,15 @@ function useAiAssist(params) {
|
|
|
83
112
|
? settings.defaultProvider
|
|
84
113
|
: (_c = (_b = providers[0]) === null || _b === void 0 ? void 0 : _b.provider) !== null && _c !== void 0 ? _c : 'copy-paste';
|
|
85
114
|
return providers.map((config) => {
|
|
86
|
-
var _a
|
|
115
|
+
var _a;
|
|
87
116
|
let isAvailable = true;
|
|
88
117
|
let unavailableReason;
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
const descriptor = ts_extras_1.AiAssist.getProviderDescriptor(config.provider).orDefault();
|
|
119
|
+
// Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip
|
|
120
|
+
// the keystore check entirely — they're available as long as the
|
|
121
|
+
// descriptor exists. Providers with `needsSecret: true` still require
|
|
122
|
+
// a configured secretName and an unlocked keystore.
|
|
123
|
+
if (config.provider !== 'copy-paste' && (descriptor === null || descriptor === void 0 ? void 0 : descriptor.needsSecret) !== false) {
|
|
91
124
|
if (!config.secretName) {
|
|
92
125
|
isAvailable = false;
|
|
93
126
|
unavailableReason = 'No API key secret configured';
|
|
@@ -108,7 +141,7 @@ function useAiAssist(params) {
|
|
|
108
141
|
}
|
|
109
142
|
}
|
|
110
143
|
}
|
|
111
|
-
const label = (
|
|
144
|
+
const label = (_a = descriptor === null || descriptor === void 0 ? void 0 : descriptor.buttonLabel) !== null && _a !== void 0 ? _a : config.provider;
|
|
112
145
|
return {
|
|
113
146
|
provider: config.provider,
|
|
114
147
|
label,
|
|
@@ -138,16 +171,9 @@ function useAiAssist(params) {
|
|
|
138
171
|
return (0, ts_utils_1.fail)(descriptorResult.message);
|
|
139
172
|
}
|
|
140
173
|
const descriptor = descriptorResult.value;
|
|
141
|
-
|
|
142
|
-
return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
|
|
143
|
-
}
|
|
144
|
-
if (!keyStore) {
|
|
145
|
-
return (0, ts_utils_1.fail)('No keystore available');
|
|
146
|
-
}
|
|
147
|
-
// Get API key from keystore
|
|
148
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
174
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
149
175
|
if (apiKeyResult.isFailure()) {
|
|
150
|
-
return (0, ts_utils_1.fail)(
|
|
176
|
+
return (0, ts_utils_1.fail)(apiKeyResult.message);
|
|
151
177
|
}
|
|
152
178
|
setIsWorking(true);
|
|
153
179
|
try {
|
|
@@ -163,6 +189,7 @@ function useAiAssist(params) {
|
|
|
163
189
|
prompt,
|
|
164
190
|
additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
|
|
165
191
|
modelOverride: providerConfig.model,
|
|
192
|
+
endpoint: providerConfig.endpoint,
|
|
166
193
|
logger,
|
|
167
194
|
tools: effectiveTools.length > 0 ? effectiveTools : undefined
|
|
168
195
|
};
|
|
@@ -238,15 +265,9 @@ function useAiAssist(params) {
|
|
|
238
265
|
if (!ts_extras_1.AiAssist.supportsImageGeneration(descriptor)) {
|
|
239
266
|
return (0, ts_utils_1.fail)(`Provider "${provider}" does not support image generation`);
|
|
240
267
|
}
|
|
241
|
-
|
|
242
|
-
return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
|
|
243
|
-
}
|
|
244
|
-
if (!keyStore) {
|
|
245
|
-
return (0, ts_utils_1.fail)('No keystore available');
|
|
246
|
-
}
|
|
247
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
268
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
248
269
|
if (apiKeyResult.isFailure()) {
|
|
249
|
-
return (0, ts_utils_1.fail)(
|
|
270
|
+
return (0, ts_utils_1.fail)(apiKeyResult.message);
|
|
250
271
|
}
|
|
251
272
|
setIsWorking(true);
|
|
252
273
|
try {
|
|
@@ -255,6 +276,7 @@ function useAiAssist(params) {
|
|
|
255
276
|
apiKey: apiKeyResult.value,
|
|
256
277
|
params,
|
|
257
278
|
modelOverride: providerConfig.model,
|
|
279
|
+
endpoint: providerConfig.endpoint,
|
|
258
280
|
logger,
|
|
259
281
|
signal
|
|
260
282
|
};
|
|
@@ -281,17 +303,11 @@ function useAiAssist(params) {
|
|
|
281
303
|
return (0, ts_utils_1.fail)(descriptorResult.message);
|
|
282
304
|
}
|
|
283
305
|
const descriptor = descriptorResult.value;
|
|
284
|
-
|
|
285
|
-
return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
|
|
286
|
-
}
|
|
287
|
-
if (!keyStore) {
|
|
288
|
-
return (0, ts_utils_1.fail)('No keystore available');
|
|
289
|
-
}
|
|
290
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
306
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
291
307
|
if (apiKeyResult.isFailure()) {
|
|
292
|
-
return (0, ts_utils_1.fail)(
|
|
308
|
+
return (0, ts_utils_1.fail)(apiKeyResult.message);
|
|
293
309
|
}
|
|
294
|
-
const requestParams = Object.assign(Object.assign({ descriptor, apiKey: apiKeyResult.value }, (capability !== undefined ? { capability } : {})), { logger,
|
|
310
|
+
const requestParams = Object.assign(Object.assign({ descriptor, apiKey: apiKeyResult.value }, (capability !== undefined ? { capability } : {})), { endpoint: providerConfig.endpoint, logger,
|
|
295
311
|
signal });
|
|
296
312
|
const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.corsRestricted);
|
|
297
313
|
const result = useProxy
|
|
@@ -313,15 +329,9 @@ function useAiAssist(params) {
|
|
|
313
329
|
return (0, ts_utils_1.fail)(descriptorResult.message);
|
|
314
330
|
}
|
|
315
331
|
const descriptor = descriptorResult.value;
|
|
316
|
-
|
|
317
|
-
return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
|
|
318
|
-
}
|
|
319
|
-
if (!keyStore) {
|
|
320
|
-
return (0, ts_utils_1.fail)('No keystore available');
|
|
321
|
-
}
|
|
322
|
-
const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
|
|
332
|
+
const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
|
|
323
333
|
if (apiKeyResult.isFailure()) {
|
|
324
|
-
return (0, ts_utils_1.fail)(
|
|
334
|
+
return (0, ts_utils_1.fail)(apiKeyResult.message);
|
|
325
335
|
}
|
|
326
336
|
const effectiveTools = ts_extras_1.AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options === null || options === void 0 ? void 0 : options.tools);
|
|
327
337
|
const requestParams = {
|
|
@@ -330,6 +340,7 @@ function useAiAssist(params) {
|
|
|
330
340
|
prompt,
|
|
331
341
|
messagesBefore: options === null || options === void 0 ? void 0 : options.messagesBefore,
|
|
332
342
|
modelOverride: providerConfig.model,
|
|
343
|
+
endpoint: providerConfig.endpoint,
|
|
333
344
|
logger,
|
|
334
345
|
tools: effectiveTools.length > 0 ? effectiveTools : undefined,
|
|
335
346
|
signal: options === null || options === void 0 ? void 0 : options.signal
|
|
@@ -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 * 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 if (config.provider !== 'copy-paste') {\n // API-based providers need a secret name and unlocked keystore with that secret\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 =\n AiAssist.getProviderDescriptor(config.provider).orDefault()?.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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n // Get API key from keystore\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${apiKeyResult.message}`);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\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 if (!providerConfig.secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n\n const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);\n if (apiKeyResult.isFailure()) {\n return fail(`Failed to get API key: ${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 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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fgv/ts-app-shell",
|
|
3
|
-
"version": "5.1.0-
|
|
3
|
+
"version": "5.1.0-21",
|
|
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",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@heroicons/react": "~2.2.0",
|
|
19
19
|
"tslib": "^2.8.1",
|
|
20
|
-
"@fgv/ts-utils": "5.1.0-
|
|
21
|
-
"@fgv/ts-extras": "5.1.0-
|
|
20
|
+
"@fgv/ts-utils": "5.1.0-21",
|
|
21
|
+
"@fgv/ts-extras": "5.1.0-21"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"react": ">=18.0.0",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"@rushstack/heft-jest-plugin": "1.2.6",
|
|
52
52
|
"@testing-library/dom": "^10.4.0",
|
|
53
53
|
"@rushstack/heft-node-rig": "2.11.27",
|
|
54
|
-
"@fgv/heft-dual-rig": "5.1.0-
|
|
55
|
-
"@fgv/ts-utils-jest": "5.1.0-
|
|
54
|
+
"@fgv/heft-dual-rig": "5.1.0-21",
|
|
55
|
+
"@fgv/ts-utils-jest": "5.1.0-21"
|
|
56
56
|
},
|
|
57
57
|
"exports": {
|
|
58
58
|
".": {
|