@hifullmoon/aicommit 2.0.1 → 2.2.0
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/.aicommit.config.example.json +0 -10
- package/CHANGELOG.md +24 -1
- package/README.md +37 -37
- package/README.zh-CN.md +37 -37
- package/SECURITY.md +0 -1
- package/bin/aicommit.js +0 -18
- package/docs/distribution.md +1 -1
- package/docs/privacy.md +18 -20
- package/docs/provider-compatibility.md +13 -25
- package/docs/team-policy.md +2 -2
- package/docs/troubleshooting.md +16 -19
- package/package.json +1 -1
- package/presets/provider-presets.json +61 -1
- package/src/api.js +3 -20
- package/src/cli.js +16 -130
- package/src/completion.js +1 -16
- package/src/config-command.js +24 -6
- package/src/config-paths.js +31 -0
- package/src/config.js +22 -42
- package/src/doctor.js +1 -12
- package/src/git.js +1 -1
- package/src/main.js +2 -26
- package/src/provider-presets.js +5 -95
- package/src/providers.js +5 -6
- package/src/setup.js +18 -4
- package/src/split.js +4 -119
- package/src/utils.js +1 -1
- package/docs/examples/extension/aicommit-extension.json +0 -9
- package/docs/examples/extension/index.mjs +0 -32
- package/docs/extensions.md +0 -93
- package/docs/provider-presets.md +0 -113
- package/schemas/aicommit-extension.schema.json +0 -27
- package/src/extension-runner.mjs +0 -36
- package/src/extensions.js +0 -426
- package/src/metrics.js +0 -375
- package/src/preset-command.js +0 -91
package/src/extensions.js
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
3
|
-
import { basename, dirname, isAbsolute, resolve } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
|
|
6
|
-
import { redactSensitiveUrl } from './utils.js';
|
|
7
|
-
|
|
8
|
-
const RUNNER_PATH = fileURLToPath(new URL('./extension-runner.mjs', import.meta.url));
|
|
9
|
-
const EXTENSION_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
10
|
-
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
11
|
-
const CAPABILITIES = new Set(['contextProvider', 'messageValidator', 'providerAdapter']);
|
|
12
|
-
const MAX_MANIFEST_BYTES = 32_000;
|
|
13
|
-
const MAX_RESULT_CHARS = 128_000;
|
|
14
|
-
const SECRET_FIELD =
|
|
15
|
-
/^(?:authorization|proxy[-_]?authorization|cookie|x[-_]?api[-_]?key|api[-_]?key|access[-_]?token|refresh[-_]?token|bearer[-_]?token|token|password|passwd|secret(?:[-_]?key)?|client[-_]?secret|credential)$/i;
|
|
16
|
-
|
|
17
|
-
export const EXTENSION_HOST = Symbol.for('aicommit.extensionHost');
|
|
18
|
-
|
|
19
|
-
function object(value) {
|
|
20
|
-
return value && typeof value === 'object' && !Array.isArray(value);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function strictKeys(value, allowed, label) {
|
|
24
|
-
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
25
|
-
if (unknown.length) throw new Error(`${label} contains unknown field(s): ${unknown.join(', ')}.`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isExtensionProviderType(value) {
|
|
29
|
-
return (
|
|
30
|
-
typeof value === 'string' &&
|
|
31
|
-
value.startsWith('extension:') &&
|
|
32
|
-
EXTENSION_ID.test(value.slice(10))
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function validateExtensionsConfig(value) {
|
|
37
|
-
if (!object(value)) throw new Error('Invalid config "extensions": expected an object.');
|
|
38
|
-
strictKeys(value, new Set(['manifests', 'timeoutMs', 'maxContextChars']), 'Config "extensions"');
|
|
39
|
-
if (
|
|
40
|
-
!Array.isArray(value.manifests) ||
|
|
41
|
-
value.manifests.length > 20 ||
|
|
42
|
-
value.manifests.some(
|
|
43
|
-
(path) => typeof path !== 'string' || !isAbsolute(path) || path.includes('\0'),
|
|
44
|
-
)
|
|
45
|
-
) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
'Invalid config "extensions.manifests": expected at most 20 absolute manifest paths.',
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
if (!Number.isInteger(value.timeoutMs) || value.timeoutMs < 100 || value.timeoutMs > 30_000) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
'Invalid config "extensions.timeoutMs": expected an integer between 100 and 30000.',
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
if (
|
|
56
|
-
!Number.isInteger(value.maxContextChars) ||
|
|
57
|
-
value.maxContextChars < 0 ||
|
|
58
|
-
value.maxContextChars > 10_000
|
|
59
|
-
) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
'Invalid config "extensions.maxContextChars": expected an integer between 0 and 10000.',
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
return value;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function validateManifest(value, manifestPath) {
|
|
68
|
-
if (!object(value)) throw new Error(`Extension manifest ${manifestPath} must be an object.`);
|
|
69
|
-
strictKeys(
|
|
70
|
-
value,
|
|
71
|
-
new Set(['kind', 'apiVersion', 'id', 'version', 'entry', 'capabilities', 'permissions']),
|
|
72
|
-
`Extension manifest ${manifestPath}`,
|
|
73
|
-
);
|
|
74
|
-
if (value.kind !== 'aicommit-extension') throw new Error(`${manifestPath}: invalid kind.`);
|
|
75
|
-
if (value.apiVersion !== 1) throw new Error(`${manifestPath}: apiVersion must be 1.`);
|
|
76
|
-
if (!EXTENSION_ID.test(value.id || '')) throw new Error(`${manifestPath}: invalid extension id.`);
|
|
77
|
-
if (!SEMVER.test(value.version || '')) throw new Error(`${manifestPath}: invalid version.`);
|
|
78
|
-
if (
|
|
79
|
-
typeof value.entry !== 'string' ||
|
|
80
|
-
!value.entry.endsWith('.mjs') ||
|
|
81
|
-
isAbsolute(value.entry) ||
|
|
82
|
-
value.entry.includes('\0')
|
|
83
|
-
) {
|
|
84
|
-
throw new Error(`${manifestPath}: entry must be a relative .mjs path.`);
|
|
85
|
-
}
|
|
86
|
-
if (
|
|
87
|
-
!Array.isArray(value.capabilities) ||
|
|
88
|
-
value.capabilities.length === 0 ||
|
|
89
|
-
new Set(value.capabilities).size !== value.capabilities.length ||
|
|
90
|
-
value.capabilities.some((name) => !CAPABILITIES.has(name))
|
|
91
|
-
) {
|
|
92
|
-
throw new Error(`${manifestPath}: capabilities contain an unsupported or duplicate value.`);
|
|
93
|
-
}
|
|
94
|
-
if (!object(value.permissions))
|
|
95
|
-
throw new Error(`${manifestPath}: permissions must be an object.`);
|
|
96
|
-
strictKeys(value.permissions, new Set(['credentials']), `${manifestPath} permissions`);
|
|
97
|
-
if (value.permissions.credentials !== false) {
|
|
98
|
-
throw new Error(`${manifestPath}: permissions.credentials must be false in extension API v1.`);
|
|
99
|
-
}
|
|
100
|
-
return value;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function loadManifest(manifestPath) {
|
|
104
|
-
const manifestStat = await lstat(manifestPath);
|
|
105
|
-
if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) {
|
|
106
|
-
throw new Error(`Extension manifest must be a regular non-symlink file: ${manifestPath}`);
|
|
107
|
-
}
|
|
108
|
-
if (manifestStat.size > MAX_MANIFEST_BYTES) {
|
|
109
|
-
throw new Error(`Extension manifest exceeds ${MAX_MANIFEST_BYTES} bytes: ${manifestPath}`);
|
|
110
|
-
}
|
|
111
|
-
const canonicalManifest = await realpath(manifestPath);
|
|
112
|
-
let parsed;
|
|
113
|
-
try {
|
|
114
|
-
parsed = JSON.parse(await readFile(canonicalManifest, 'utf8'));
|
|
115
|
-
} catch (error) {
|
|
116
|
-
throw new Error(`Failed to parse extension manifest ${manifestPath}: ${error.message}`);
|
|
117
|
-
}
|
|
118
|
-
const manifest = validateManifest(parsed, manifestPath);
|
|
119
|
-
const root = dirname(canonicalManifest);
|
|
120
|
-
const candidate = resolve(root, manifest.entry);
|
|
121
|
-
const prefix = `${root}${process.platform === 'win32' ? '\\' : '/'}`;
|
|
122
|
-
if (!candidate.startsWith(prefix))
|
|
123
|
-
throw new Error(`${manifestPath}: entry escapes its directory.`);
|
|
124
|
-
const entryStat = await lstat(candidate);
|
|
125
|
-
if (!entryStat.isFile() || entryStat.isSymbolicLink()) {
|
|
126
|
-
throw new Error(`${manifestPath}: entry must be a regular non-symlink file.`);
|
|
127
|
-
}
|
|
128
|
-
const entry = await realpath(candidate);
|
|
129
|
-
if (!entry.startsWith(prefix)) throw new Error(`${manifestPath}: entry escapes its directory.`);
|
|
130
|
-
return Object.freeze({ ...manifest, manifestPath: canonicalManifest, entry });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function permissionArgs(version = process.versions.node) {
|
|
134
|
-
const major = Number(version.split('.')[0]);
|
|
135
|
-
if (major < 20) {
|
|
136
|
-
throw new Error(
|
|
137
|
-
'Executable extensions require Node.js 20 or newer for credential-deny isolation. Core aicommit remains supported on Node.js 18 when extensions are disabled.',
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
return [major >= 23 ? '--permission' : '--experimental-permission'];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function safeEnvironment() {
|
|
144
|
-
return {
|
|
145
|
-
PATH: process.env.PATH || '',
|
|
146
|
-
LANG: process.env.LANG || 'C',
|
|
147
|
-
LC_ALL: process.env.LC_ALL || '',
|
|
148
|
-
NO_COLOR: '1',
|
|
149
|
-
NODE_NO_WARNINGS: '1',
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function invokeExtension(extension, capability, input, timeoutMs) {
|
|
154
|
-
return new Promise((resolvePromise, reject) => {
|
|
155
|
-
const args = [
|
|
156
|
-
...permissionArgs(),
|
|
157
|
-
`--allow-fs-read=${RUNNER_PATH}`,
|
|
158
|
-
`--allow-fs-read=${extension.entry}`,
|
|
159
|
-
RUNNER_PATH,
|
|
160
|
-
];
|
|
161
|
-
const child = spawn(process.execPath, args, {
|
|
162
|
-
cwd: dirname(extension.entry),
|
|
163
|
-
env: safeEnvironment(),
|
|
164
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
165
|
-
windowsHide: true,
|
|
166
|
-
});
|
|
167
|
-
let stdout = '';
|
|
168
|
-
let stderr = '';
|
|
169
|
-
let settled = false;
|
|
170
|
-
const finish = (fn, value) => {
|
|
171
|
-
if (settled) return;
|
|
172
|
-
settled = true;
|
|
173
|
-
clearTimeout(timer);
|
|
174
|
-
fn(value);
|
|
175
|
-
};
|
|
176
|
-
const timer = setTimeout(() => {
|
|
177
|
-
child.kill('SIGKILL');
|
|
178
|
-
finish(reject, new Error(`Extension ${extension.id} timed out after ${timeoutMs}ms.`));
|
|
179
|
-
}, timeoutMs);
|
|
180
|
-
child.stdout.setEncoding('utf8');
|
|
181
|
-
child.stderr.setEncoding('utf8');
|
|
182
|
-
child.stdout.on('data', (chunk) => {
|
|
183
|
-
stdout += chunk;
|
|
184
|
-
if (stdout.length > MAX_RESULT_CHARS) {
|
|
185
|
-
child.kill('SIGKILL');
|
|
186
|
-
finish(reject, new Error(`Extension ${extension.id} output exceeded the safety limit.`));
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
child.stderr.on('data', (chunk) => {
|
|
190
|
-
stderr += chunk;
|
|
191
|
-
if (stderr.length > MAX_RESULT_CHARS) {
|
|
192
|
-
child.kill('SIGKILL');
|
|
193
|
-
finish(
|
|
194
|
-
reject,
|
|
195
|
-
new Error(`Extension ${extension.id} diagnostics exceeded the safety limit.`),
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
child.on('error', (error) => finish(reject, error));
|
|
200
|
-
child.on('close', () => {
|
|
201
|
-
if (settled) return;
|
|
202
|
-
try {
|
|
203
|
-
const response = JSON.parse(stdout.trim());
|
|
204
|
-
if (!response?.ok) {
|
|
205
|
-
throw new Error(response?.error || stderr.trim() || 'extension process failed');
|
|
206
|
-
}
|
|
207
|
-
finish(resolvePromise, response.result);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
finish(
|
|
210
|
-
reject,
|
|
211
|
-
new Error(
|
|
212
|
-
`Extension ${extension.id} failed: ${error.message}${stderr ? ` (${stderr.trim().slice(0, 500)})` : ''}`,
|
|
213
|
-
),
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
child.stdin.end(JSON.stringify({ entry: extension.entry, capability, input }));
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function containsCredentialField(value, depth = 0) {
|
|
222
|
-
if (depth > 12) return true;
|
|
223
|
-
if (!value || typeof value !== 'object') return false;
|
|
224
|
-
if (Array.isArray(value)) return value.some((item) => containsCredentialField(item, depth + 1));
|
|
225
|
-
return Object.entries(value).some(
|
|
226
|
-
([key, item]) => SECRET_FIELD.test(key) || containsCredentialField(item, depth + 1),
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function safeAdapterConfig(config) {
|
|
231
|
-
if (containsCredentialField(config.extraBody) || containsCredentialField(config.reasoning)) {
|
|
232
|
-
throw new Error(
|
|
233
|
-
'Extension provider configuration contains a credential-like field that cannot cross the extension boundary.',
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
apiUrl: redactSensitiveUrl(config.apiUrl),
|
|
238
|
-
modelId: config.modelId,
|
|
239
|
-
extraBody: config.extraBody,
|
|
240
|
-
reasoning: config.reasoning,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export async function createExtensionHost(settings) {
|
|
245
|
-
validateExtensionsConfig(settings);
|
|
246
|
-
const loaded = [];
|
|
247
|
-
const ids = new Set();
|
|
248
|
-
for (const path of settings.manifests) {
|
|
249
|
-
const extension = await loadManifest(path);
|
|
250
|
-
if (ids.has(extension.id)) throw new Error(`Duplicate extension id: ${extension.id}.`);
|
|
251
|
-
ids.add(extension.id);
|
|
252
|
-
loaded.push(extension);
|
|
253
|
-
}
|
|
254
|
-
if (loaded.length) permissionArgs();
|
|
255
|
-
const byCapability = (name) => loaded.filter((item) => item.capabilities.includes(name));
|
|
256
|
-
const findProvider = (providerType) => {
|
|
257
|
-
const id = providerType.slice(10);
|
|
258
|
-
const extension = loaded.find(
|
|
259
|
-
(item) => item.id === id && item.capabilities.includes('providerAdapter'),
|
|
260
|
-
);
|
|
261
|
-
if (!extension)
|
|
262
|
-
throw new Error(`Provider extension "${id}" is not installed or lacks providerAdapter.`);
|
|
263
|
-
return extension;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return Object.freeze({
|
|
267
|
-
extensions: loaded.map(({ id, version, capabilities }) => ({ id, version, capabilities })),
|
|
268
|
-
async collectContext({ files = [], branch = '' }) {
|
|
269
|
-
const parts = [];
|
|
270
|
-
const warnings = [];
|
|
271
|
-
for (const extension of byCapability('contextProvider')) {
|
|
272
|
-
try {
|
|
273
|
-
const result = await invokeExtension(
|
|
274
|
-
extension,
|
|
275
|
-
'contextProvider',
|
|
276
|
-
{
|
|
277
|
-
repository: basename(process.cwd()),
|
|
278
|
-
branch: String(branch).slice(0, 300),
|
|
279
|
-
files: files.slice(0, 500).map(({ status, path }) => ({
|
|
280
|
-
status: String(status).slice(0, 10),
|
|
281
|
-
path: String(path).slice(0, 500),
|
|
282
|
-
})),
|
|
283
|
-
},
|
|
284
|
-
settings.timeoutMs,
|
|
285
|
-
);
|
|
286
|
-
if (!object(result) || typeof result.text !== 'string') {
|
|
287
|
-
throw new Error('contextProvider must return { text, warnings? }.');
|
|
288
|
-
}
|
|
289
|
-
const remaining = settings.maxContextChars - parts.join('\n').length;
|
|
290
|
-
if (remaining > 0 && result.text.trim()) {
|
|
291
|
-
parts.push(`[extension:${extension.id}]\n${result.text.trim().slice(0, remaining)}`);
|
|
292
|
-
}
|
|
293
|
-
if (Array.isArray(result.warnings)) {
|
|
294
|
-
warnings.push(
|
|
295
|
-
...result.warnings
|
|
296
|
-
.filter((item) => typeof item === 'string')
|
|
297
|
-
.slice(0, 10)
|
|
298
|
-
.map((item) => `[extension:${extension.id}] ${item.slice(0, 500)}`),
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
} catch (error) {
|
|
302
|
-
warnings.push(`[extension:${extension.id}] context unavailable: ${error.message}`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return { text: parts.join('\n\n').slice(0, settings.maxContextChars), warnings };
|
|
306
|
-
},
|
|
307
|
-
async validateMessage(message, policy) {
|
|
308
|
-
const issues = [];
|
|
309
|
-
for (const extension of byCapability('messageValidator')) {
|
|
310
|
-
const result = await invokeExtension(
|
|
311
|
-
extension,
|
|
312
|
-
'messageValidator',
|
|
313
|
-
{ message: String(message).slice(0, 20_000), policy },
|
|
314
|
-
settings.timeoutMs,
|
|
315
|
-
);
|
|
316
|
-
if (!object(result) || !Array.isArray(result.issues)) {
|
|
317
|
-
throw new Error(`Extension ${extension.id} messageValidator must return { issues: [] }.`);
|
|
318
|
-
}
|
|
319
|
-
for (const issue of result.issues.slice(0, 50)) {
|
|
320
|
-
if (
|
|
321
|
-
!object(issue) ||
|
|
322
|
-
!['error', 'warning'].includes(issue.severity) ||
|
|
323
|
-
typeof issue.code !== 'string' ||
|
|
324
|
-
!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(issue.code) ||
|
|
325
|
-
typeof issue.message !== 'string' ||
|
|
326
|
-
!issue.message.trim()
|
|
327
|
-
) {
|
|
328
|
-
throw new Error(`Extension ${extension.id} returned an invalid validation issue.`);
|
|
329
|
-
}
|
|
330
|
-
issues.push({
|
|
331
|
-
severity: issue.severity,
|
|
332
|
-
code: `extension:${extension.id}:${issue.code}`,
|
|
333
|
-
message: `[extension:${extension.id}] ${issue.message.trim().slice(0, 500)}`,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return issues;
|
|
338
|
-
},
|
|
339
|
-
providerAdapter(config) {
|
|
340
|
-
const extension = findProvider(config.providerType);
|
|
341
|
-
const capabilities = Object.freeze({
|
|
342
|
-
streaming: false,
|
|
343
|
-
reasoning: 'configurable',
|
|
344
|
-
tokenBudget: 'extension',
|
|
345
|
-
usage: true,
|
|
346
|
-
finishReason: true,
|
|
347
|
-
});
|
|
348
|
-
const call = (operation, input) =>
|
|
349
|
-
invokeExtension(
|
|
350
|
-
extension,
|
|
351
|
-
'providerAdapter',
|
|
352
|
-
{ operation, config: safeAdapterConfig(config), ...input },
|
|
353
|
-
settings.timeoutMs,
|
|
354
|
-
);
|
|
355
|
-
return Object.freeze({
|
|
356
|
-
id: `extension:${extension.id}`,
|
|
357
|
-
capabilities,
|
|
358
|
-
headers: {},
|
|
359
|
-
async buildRequest(request) {
|
|
360
|
-
if (
|
|
361
|
-
containsCredentialField(request?.extraBody) ||
|
|
362
|
-
containsCredentialField(request?.reasoning)
|
|
363
|
-
) {
|
|
364
|
-
throw new Error(
|
|
365
|
-
'Extension provider request contains a credential-like field that cannot cross the extension boundary.',
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
const payload = await call('buildRequest', { request });
|
|
369
|
-
if (!object(payload))
|
|
370
|
-
throw new Error('providerAdapter buildRequest must return an object.');
|
|
371
|
-
if (containsCredentialField(payload)) {
|
|
372
|
-
throw new Error('providerAdapter returned a credential-like request field.');
|
|
373
|
-
}
|
|
374
|
-
return payload;
|
|
375
|
-
},
|
|
376
|
-
async normalizeResponse(data) {
|
|
377
|
-
const result = await call('normalizeResponse', { response: data });
|
|
378
|
-
if (!object(result) || typeof result.content !== 'string') {
|
|
379
|
-
throw new Error(
|
|
380
|
-
'providerAdapter normalizeResponse must return an object with content.',
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
return {
|
|
384
|
-
provider: `extension:${extension.id}`,
|
|
385
|
-
model: typeof result.model === 'string' ? result.model : null,
|
|
386
|
-
content: result.content,
|
|
387
|
-
reasoning: typeof result.reasoning === 'string' ? result.reasoning : null,
|
|
388
|
-
usage: object(result.usage) ? result.usage : null,
|
|
389
|
-
finishReason: typeof result.finishReason === 'string' ? result.finishReason : null,
|
|
390
|
-
raw: data,
|
|
391
|
-
};
|
|
392
|
-
},
|
|
393
|
-
async reasoningForFollowUp(reasoning) {
|
|
394
|
-
if (containsCredentialField(reasoning)) {
|
|
395
|
-
throw new Error(
|
|
396
|
-
'Extension provider reasoning config contains a credential-like field that cannot cross the extension boundary.',
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
const result = await call('reasoningForFollowUp', { reasoning });
|
|
400
|
-
if (!object(result))
|
|
401
|
-
throw new Error('providerAdapter reasoningForFollowUp must return an object.');
|
|
402
|
-
return result;
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
},
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export async function configureExtensionHost(config) {
|
|
410
|
-
const host = await createExtensionHost(config.extensions);
|
|
411
|
-
Object.defineProperty(config, EXTENSION_HOST, { value: host, enumerable: true });
|
|
412
|
-
return host;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
export function extensionHostFor(config) {
|
|
416
|
-
return config?.[EXTENSION_HOST] || null;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export async function resolveProviderAdapter(config, builtInResolver) {
|
|
420
|
-
if (isExtensionProviderType(config.providerType)) {
|
|
421
|
-
const host = extensionHostFor(config);
|
|
422
|
-
if (!host) throw new Error('Provider extension runtime is not configured.');
|
|
423
|
-
return host.providerAdapter(config);
|
|
424
|
-
}
|
|
425
|
-
return builtInResolver(config);
|
|
426
|
-
}
|