@link-assistant/agent 0.26.4 → 0.26.6
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/README.md +3 -0
- package/package.json +1 -1
- package/src/cli/event-handler.js +1 -0
- package/src/cli/model-config.js +102 -18
- package/src/config/host-config-hint.ts +62 -0
- package/src/index.js +8 -0
- package/src/json-standard/index.ts +59 -1
- package/src/provider/provider.ts +36 -13
package/README.md
CHANGED
|
@@ -347,6 +347,8 @@ Commands:
|
|
|
347
347
|
|
|
348
348
|
See [docs/stdin-mode.md](../docs/stdin-mode.md) for comprehensive stdin mode documentation.
|
|
349
349
|
|
|
350
|
+
Hosts that embed the CLI supply configuration through `LINK_ASSISTANT_AGENT_CONFIG_DIR`, `LINK_ASSISTANT_AGENT_CONFIG` or `LINK_ASSISTANT_AGENT_CONFIG_CONTENT` — never by relocating `XDG_CONFIG_HOME`, which would also hide `gh`/`git` credentials from the agent's tool calls. See [docs/host-integration.md](../docs/host-integration.md).
|
|
351
|
+
|
|
350
352
|
### JSON Output Standards
|
|
351
353
|
|
|
352
354
|
The agent supports two JSON output format standards via the `--json-standard` option:
|
|
@@ -773,6 +775,7 @@ For full documentation, see the [main README](../README.md) in the repository ro
|
|
|
773
775
|
- [Tools Reference](../TOOLS.md)
|
|
774
776
|
- [Usage Examples](../EXAMPLES.md)
|
|
775
777
|
- [Testing Guide](../TESTING.md)
|
|
778
|
+
- [Host Integration](../docs/host-integration.md) — supplying config via `LINK_ASSISTANT_AGENT_CONFIG_DIR`, `LINK_ASSISTANT_AGENT_CONFIG` and `LINK_ASSISTANT_AGENT_CONFIG_CONTENT`
|
|
776
779
|
|
|
777
780
|
## License
|
|
778
781
|
|
package/package.json
CHANGED
package/src/cli/event-handler.js
CHANGED
package/src/cli/model-config.js
CHANGED
|
@@ -358,6 +358,52 @@ async function resolveCompactionModelEntry(
|
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Resolve a built-in cascade entry against the run's own provider only.
|
|
363
|
+
*
|
|
364
|
+
* The built-in cascade is narrowed to the configured provider anyway (#307),
|
|
365
|
+
* so resolving its short names across the whole provider registry only to
|
|
366
|
+
* drop the result afterwards was wasted work — and it logged records such as
|
|
367
|
+
* `{"providerID":"opencode","message":"resolved short model name"}` that a
|
|
368
|
+
* host reads as the run using OpenCode (#313). Only the base provider's own
|
|
369
|
+
* catalog is consulted here; any other provider is never looked up.
|
|
370
|
+
*
|
|
371
|
+
* @returns {Promise<{providerID: string, modelID: string, useSameModel: boolean} | null>}
|
|
372
|
+
* the entry, or `null` when the base provider does not serve it
|
|
373
|
+
* @see https://github.com/link-assistant/agent/issues/313
|
|
374
|
+
*/
|
|
375
|
+
async function resolveEntryWithinProvider(
|
|
376
|
+
modelArg,
|
|
377
|
+
baseProviderID,
|
|
378
|
+
baseModelID
|
|
379
|
+
) {
|
|
380
|
+
if (modelArg.toLowerCase() === 'same') {
|
|
381
|
+
return {
|
|
382
|
+
providerID: baseProviderID,
|
|
383
|
+
modelID: baseModelID,
|
|
384
|
+
useSameModel: true,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (modelArg.includes('/')) {
|
|
389
|
+
const parts = modelArg.split('/');
|
|
390
|
+
if (parts[0] !== baseProviderID) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
providerID: parts[0],
|
|
395
|
+
modelID: parts.slice(1).join('/'),
|
|
396
|
+
useSameModel: false,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const { Provider } = await import('../provider/provider.ts');
|
|
401
|
+
if (!(await Provider.hasModel(baseProviderID, modelArg))) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
return { providerID: baseProviderID, modelID: modelArg, useSameModel: false };
|
|
405
|
+
}
|
|
406
|
+
|
|
361
407
|
/**
|
|
362
408
|
* Narrow a built-in compaction default to the provider the run was pointed at.
|
|
363
409
|
*
|
|
@@ -376,19 +422,17 @@ async function resolveCompactionModelEntry(
|
|
|
376
422
|
* provider keeps the shipped cascade, whose extra entries are the rate-limit
|
|
377
423
|
* fallbacks it was written for.
|
|
378
424
|
*
|
|
379
|
-
* @param {{providerID: string, modelID: string, useSameModel: boolean}[]}
|
|
425
|
+
* @param {{providerID: string, modelID: string, useSameModel: boolean}[]} kept
|
|
426
|
+
* Entries served by the base provider
|
|
427
|
+
* @param {string[]} dropped Default entries, as written, that were left out
|
|
380
428
|
* @param {string} baseProviderID Provider of the model that runs the turns
|
|
381
429
|
* @param {string} baseModelID Model that runs the turns
|
|
382
430
|
* @returns {{providerID: string, modelID: string, useSameModel: boolean}[]}
|
|
383
431
|
* @see https://github.com/link-assistant/agent/issues/307
|
|
384
432
|
*/
|
|
385
|
-
function inheritBaseProvider(
|
|
386
|
-
const kept = entries.filter(
|
|
387
|
-
(entry) => entry.useSameModel || entry.providerID === baseProviderID
|
|
388
|
-
);
|
|
389
|
-
const dropped = entries.filter((entry) => !kept.includes(entry));
|
|
433
|
+
function inheritBaseProvider(kept, dropped, baseProviderID, baseModelID) {
|
|
390
434
|
if (dropped.length === 0) {
|
|
391
|
-
return
|
|
435
|
+
return kept;
|
|
392
436
|
}
|
|
393
437
|
|
|
394
438
|
const inherited =
|
|
@@ -407,7 +451,9 @@ function inheritBaseProvider(entries, baseProviderID, baseModelID) {
|
|
|
407
451
|
hint: 'Secondary calls (compaction, session summary) inherit --model unless a compaction model is named explicitly',
|
|
408
452
|
baseProviderID,
|
|
409
453
|
baseModelID,
|
|
410
|
-
|
|
454
|
+
// The names as written in the default: they were never resolved, so they
|
|
455
|
+
// say nothing about which provider would have served them (#313).
|
|
456
|
+
dropped,
|
|
411
457
|
kept: inherited.map((entry) =>
|
|
412
458
|
entry.useSameModel ? 'same' : `${entry.providerID}/${entry.modelID}`
|
|
413
459
|
),
|
|
@@ -459,9 +505,30 @@ async function parseCompactionModelConfig(
|
|
|
459
505
|
const modelNames = parseLinksNotationSequence(compactionModelsArg);
|
|
460
506
|
|
|
461
507
|
if (modelNames.length > 0) {
|
|
462
|
-
//
|
|
508
|
+
// A cascade nobody asked for must not reach across providers (#307), and
|
|
509
|
+
// is not even resolved against other providers (#313).
|
|
510
|
+
const builtInCascade =
|
|
511
|
+
compactionModelsSource === 'default' &&
|
|
512
|
+
getDefaultCompactionModelsSource(defaultOptions) === 'default';
|
|
513
|
+
const usingBuiltInCascade =
|
|
514
|
+
builtInCascade && baseProviderID !== DEFAULT_PROVIDER_ID;
|
|
515
|
+
|
|
463
516
|
const compactionModels = [];
|
|
517
|
+
const unavailable = [];
|
|
464
518
|
for (const name of modelNames) {
|
|
519
|
+
if (usingBuiltInCascade) {
|
|
520
|
+
const entry = await resolveEntryWithinProvider(
|
|
521
|
+
name,
|
|
522
|
+
baseProviderID,
|
|
523
|
+
baseModelID
|
|
524
|
+
);
|
|
525
|
+
if (entry) {
|
|
526
|
+
compactionModels.push(entry);
|
|
527
|
+
} else {
|
|
528
|
+
unavailable.push(name);
|
|
529
|
+
}
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
465
532
|
try {
|
|
466
533
|
const resolved = await resolveCompactionModelEntry(
|
|
467
534
|
name,
|
|
@@ -474,6 +541,12 @@ async function parseCompactionModelConfig(
|
|
|
474
541
|
useSameModel: resolved.useSameModel,
|
|
475
542
|
});
|
|
476
543
|
} catch (err) {
|
|
544
|
+
// A model nobody asked for is not an error worth a record of its
|
|
545
|
+
// own (#313); the summary below lists it.
|
|
546
|
+
if (builtInCascade) {
|
|
547
|
+
unavailable.push(name);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
477
550
|
const logSkip =
|
|
478
551
|
compactionModelsSource === 'default'
|
|
479
552
|
? Log.Default.debug
|
|
@@ -486,13 +559,13 @@ async function parseCompactionModelConfig(
|
|
|
486
559
|
}
|
|
487
560
|
}
|
|
488
561
|
|
|
489
|
-
// A cascade nobody asked for must not reach across providers (#307).
|
|
490
|
-
const usingBuiltInCascade =
|
|
491
|
-
compactionModelsSource === 'default' &&
|
|
492
|
-
getDefaultCompactionModelsSource(defaultOptions) === 'default' &&
|
|
493
|
-
baseProviderID !== DEFAULT_PROVIDER_ID;
|
|
494
562
|
const cascade = usingBuiltInCascade
|
|
495
|
-
? inheritBaseProvider(
|
|
563
|
+
? inheritBaseProvider(
|
|
564
|
+
compactionModels,
|
|
565
|
+
unavailable,
|
|
566
|
+
baseProviderID,
|
|
567
|
+
baseModelID
|
|
568
|
+
)
|
|
496
569
|
: compactionModels;
|
|
497
570
|
|
|
498
571
|
Log.Default.info(() => ({
|
|
@@ -501,6 +574,9 @@ async function parseCompactionModelConfig(
|
|
|
501
574
|
m.useSameModel ? 'same' : `${m.providerID}/${m.modelID}`
|
|
502
575
|
),
|
|
503
576
|
source: compactionModelsSource,
|
|
577
|
+
...(unavailable.length > 0 && !usingBuiltInCascade
|
|
578
|
+
? { unavailable }
|
|
579
|
+
: {}),
|
|
504
580
|
}));
|
|
505
581
|
|
|
506
582
|
// Use the first model as the primary compaction model (for backward compatibility)
|
|
@@ -543,9 +619,17 @@ async function parseCompactionModelConfig(
|
|
|
543
619
|
compactionModelSource === 'default' &&
|
|
544
620
|
getDefaultCompactionModelSource(defaultOptions) === 'default' &&
|
|
545
621
|
baseProviderID !== DEFAULT_PROVIDER_ID;
|
|
546
|
-
const [entry] =
|
|
547
|
-
|
|
548
|
-
|
|
622
|
+
const [entry] =
|
|
623
|
+
usingBuiltInCompactionModel &&
|
|
624
|
+
!resolved.useSameModel &&
|
|
625
|
+
resolved.providerID !== baseProviderID
|
|
626
|
+
? inheritBaseProvider(
|
|
627
|
+
[],
|
|
628
|
+
[compactionModelArg],
|
|
629
|
+
baseProviderID,
|
|
630
|
+
baseModelID
|
|
631
|
+
)
|
|
632
|
+
: [resolved];
|
|
549
633
|
|
|
550
634
|
Log.Default.info(() => ({
|
|
551
635
|
message: 'using single compaction model',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Host integration contract: how an embedding host hands the CLI its config.
|
|
6
|
+
*
|
|
7
|
+
* A host supplies configuration through the CLI's own variables:
|
|
8
|
+
*
|
|
9
|
+
* LINK_ASSISTANT_AGENT_CONFIG_DIR a directory holding opencode.json(c)
|
|
10
|
+
* LINK_ASSISTANT_AGENT_CONFIG a single config file
|
|
11
|
+
* LINK_ASSISTANT_AGENT_CONFIG_CONTENT the config itself, as JSON
|
|
12
|
+
*
|
|
13
|
+
* Relocating `XDG_CONFIG_HOME` to feed the CLI a generated config also moves
|
|
14
|
+
* the config home of every tool the agent runs — `gh` then finds no
|
|
15
|
+
* `hosts.yml` and the session loses GitHub authentication (#314). This module
|
|
16
|
+
* spots that setup so the CLI can point the host at the variables above.
|
|
17
|
+
*
|
|
18
|
+
* @see https://github.com/link-assistant/agent/issues/314
|
|
19
|
+
* @see ../../../docs/host-integration.md
|
|
20
|
+
*/
|
|
21
|
+
export namespace HostConfigHint {
|
|
22
|
+
export const CONFIG_ENV_VARS = [
|
|
23
|
+
'LINK_ASSISTANT_AGENT_CONFIG_DIR',
|
|
24
|
+
'LINK_ASSISTANT_AGENT_CONFIG',
|
|
25
|
+
'LINK_ASSISTANT_AGENT_CONFIG_CONTENT',
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export interface Hint {
|
|
29
|
+
message: string;
|
|
30
|
+
xdgConfigHome: string;
|
|
31
|
+
realConfigHome: string;
|
|
32
|
+
hint: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return a hint when `XDG_CONFIG_HOME` points away from the user's real
|
|
37
|
+
* config home and none of the CLI's own config variables is set — the
|
|
38
|
+
* signature of a host relocating XDG to deliver config. Pure, so it can be
|
|
39
|
+
* tested with any environment.
|
|
40
|
+
*/
|
|
41
|
+
export function detect(
|
|
42
|
+
env: Record<string, string | undefined> = process.env,
|
|
43
|
+
homedir: string = os.homedir()
|
|
44
|
+
): Hint | undefined {
|
|
45
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
46
|
+
if (!xdgConfigHome) return undefined;
|
|
47
|
+
|
|
48
|
+
const realConfigHome = path.join(homedir, '.config');
|
|
49
|
+
if (path.resolve(xdgConfigHome) === path.resolve(realConfigHome)) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (CONFIG_ENV_VARS.some((name) => env[name]?.trim())) return undefined;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
message: 'XDG_CONFIG_HOME is relocated',
|
|
57
|
+
xdgConfigHome,
|
|
58
|
+
realConfigHome,
|
|
59
|
+
hint: `To supply agent config, set ${CONFIG_ENV_VARS.join(', ')} instead of XDG_CONFIG_HOME: tools the agent runs (gh, git) read their config and credentials from XDG_CONFIG_HOME too`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getConfigSnapshot,
|
|
8
8
|
} from './config/config.ts';
|
|
9
9
|
import { setProcessName } from './cli/process-name.ts';
|
|
10
|
+
import { HostConfigHint } from './config/host-config-hint.ts';
|
|
10
11
|
setProcessName('agent');
|
|
11
12
|
import { Server } from './server/server.ts';
|
|
12
13
|
import { Instance } from './project/instance.ts';
|
|
@@ -909,6 +910,13 @@ async function main() {
|
|
|
909
910
|
config: getConfigSnapshot(),
|
|
910
911
|
}));
|
|
911
912
|
|
|
913
|
+
// A host that relocates XDG_CONFIG_HOME to deliver agent config also
|
|
914
|
+
// hides gh/git credentials from the agent's tool calls (#314).
|
|
915
|
+
const hostConfigHint = HostConfigHint.detect();
|
|
916
|
+
if (hostConfigHint) {
|
|
917
|
+
Log.Default.warn(() => hostConfigHint);
|
|
918
|
+
}
|
|
919
|
+
|
|
912
920
|
// Global fetch monkey-patch for verbose HTTP logging (#221).
|
|
913
921
|
if (!globalThis.__agentVerboseFetchInstalled) {
|
|
914
922
|
globalThis.fetch = createVerboseFetch(globalThis.fetch, {
|
|
@@ -28,6 +28,7 @@ export interface OpenCodeEvent {
|
|
|
28
28
|
timestamp: number;
|
|
29
29
|
sessionID: string;
|
|
30
30
|
part?: Record<string, unknown>;
|
|
31
|
+
message?: string;
|
|
31
32
|
error?: string | Record<string, unknown>;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -116,12 +117,44 @@ export function convertOpenCodeToClaude(
|
|
|
116
117
|
case 'tool_use':
|
|
117
118
|
if (event.part && event.part.state) {
|
|
118
119
|
const state = event.part.state as Record<string, unknown>;
|
|
120
|
+
const input = state.input || {};
|
|
121
|
+
const status = state.status as string | undefined;
|
|
122
|
+
|
|
123
|
+
// A newly-created pending part has not received its parsed arguments
|
|
124
|
+
// yet. Populated pending parts are retained for compatibility with
|
|
125
|
+
// producers that publish the complete call in their first snapshot.
|
|
126
|
+
if (
|
|
127
|
+
status === 'pending' &&
|
|
128
|
+
typeof input === 'object' &&
|
|
129
|
+
input !== null &&
|
|
130
|
+
Object.keys(input).length === 0
|
|
131
|
+
) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (status === 'completed') {
|
|
136
|
+
return {
|
|
137
|
+
type: 'tool_result',
|
|
138
|
+
timestamp,
|
|
139
|
+
session_id,
|
|
140
|
+
tool_use_id: event.part.id as string,
|
|
141
|
+
output: state.output as string,
|
|
142
|
+
status: 'success',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// outputBusEvent follows an errored tool snapshot with an error event
|
|
147
|
+
// carrying the same part, which is converted to the terminal result.
|
|
148
|
+
if (status === 'error') {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
return {
|
|
120
153
|
type: 'tool_use',
|
|
121
154
|
timestamp,
|
|
122
155
|
session_id,
|
|
123
156
|
name: (event.part.tool as string) || 'unknown',
|
|
124
|
-
input
|
|
157
|
+
input,
|
|
125
158
|
tool_use_id: event.part.id as string,
|
|
126
159
|
};
|
|
127
160
|
}
|
|
@@ -144,6 +177,21 @@ export function convertOpenCodeToClaude(
|
|
|
144
177
|
};
|
|
145
178
|
|
|
146
179
|
case 'error':
|
|
180
|
+
if (event.part?.type === 'tool' && event.part.state) {
|
|
181
|
+
const state = event.part.state as Record<string, unknown>;
|
|
182
|
+
return {
|
|
183
|
+
type: 'tool_result',
|
|
184
|
+
timestamp,
|
|
185
|
+
session_id,
|
|
186
|
+
tool_use_id: event.part.id as string,
|
|
187
|
+
output:
|
|
188
|
+
event.message ||
|
|
189
|
+
(typeof state.error === 'string'
|
|
190
|
+
? state.error
|
|
191
|
+
: 'Tool execution failed'),
|
|
192
|
+
status: 'error',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
147
195
|
return {
|
|
148
196
|
type: 'result',
|
|
149
197
|
timestamp,
|
|
@@ -173,6 +221,8 @@ export function createEventHandler(
|
|
|
173
221
|
) {
|
|
174
222
|
const startTime = Date.now();
|
|
175
223
|
const { model } = options;
|
|
224
|
+
const emittedToolUseIds = new Set<string>();
|
|
225
|
+
const emittedToolResultIds = new Set<string>();
|
|
176
226
|
|
|
177
227
|
return {
|
|
178
228
|
/**
|
|
@@ -184,6 +234,14 @@ export function createEventHandler(
|
|
|
184
234
|
if (standard === 'claude') {
|
|
185
235
|
const claudeEvent = convertOpenCodeToClaude(event, startTime, model);
|
|
186
236
|
if (claudeEvent) {
|
|
237
|
+
if (claudeEvent.type === 'tool_use' && claudeEvent.tool_use_id) {
|
|
238
|
+
if (emittedToolUseIds.has(claudeEvent.tool_use_id)) return;
|
|
239
|
+
emittedToolUseIds.add(claudeEvent.tool_use_id);
|
|
240
|
+
}
|
|
241
|
+
if (claudeEvent.type === 'tool_result' && claudeEvent.tool_use_id) {
|
|
242
|
+
if (emittedToolResultIds.has(claudeEvent.tool_use_id)) return;
|
|
243
|
+
emittedToolResultIds.add(claudeEvent.tool_use_id);
|
|
244
|
+
}
|
|
187
245
|
outputStream.write(serializeOutput(claudeEvent, standard));
|
|
188
246
|
}
|
|
189
247
|
} else {
|
package/src/provider/provider.ts
CHANGED
|
@@ -38,6 +38,14 @@ import { createGroq } from '@ai-sdk/groq';
|
|
|
38
38
|
|
|
39
39
|
export namespace Provider {
|
|
40
40
|
const log = Log.create({ service: 'provider' });
|
|
41
|
+
/**
|
|
42
|
+
* Catalog lookups — which providers are installed, which provider a short
|
|
43
|
+
* model name would map to — describe candidates, not the model a run uses.
|
|
44
|
+
* They are logged under their own service and never carry `providerID` /
|
|
45
|
+
* `modelID` fields, which stream consumers read as the run's model identity
|
|
46
|
+
* (#313). The active model is attested by the `model_resolved` event.
|
|
47
|
+
*/
|
|
48
|
+
const registryLog = Log.create({ service: 'provider-registry' });
|
|
41
49
|
|
|
42
50
|
/**
|
|
43
51
|
* Bundled providers - these are pre-installed and imported statically
|
|
@@ -1235,8 +1243,11 @@ export namespace Provider {
|
|
|
1235
1243
|
delete providers[providerID];
|
|
1236
1244
|
continue;
|
|
1237
1245
|
}
|
|
1238
|
-
log.info(() => ({ message: 'found', providerID }));
|
|
1239
1246
|
}
|
|
1247
|
+
registryLog.info(() => ({
|
|
1248
|
+
message: 'providers available',
|
|
1249
|
+
available: Object.keys(providers),
|
|
1250
|
+
}));
|
|
1240
1251
|
|
|
1241
1252
|
return {
|
|
1242
1253
|
models,
|
|
@@ -1696,6 +1707,15 @@ export namespace Provider {
|
|
|
1696
1707
|
return state().then((s) => s.providers[providerID]);
|
|
1697
1708
|
}
|
|
1698
1709
|
|
|
1710
|
+
/**
|
|
1711
|
+
* Whether a provider's catalog lists a model. Looks at that one provider
|
|
1712
|
+
* only and logs nothing (#313).
|
|
1713
|
+
*/
|
|
1714
|
+
export async function hasModel(providerID: string, modelID: string) {
|
|
1715
|
+
const provider = await getProvider(providerID);
|
|
1716
|
+
return Boolean(provider?.info.models[modelID]);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1699
1719
|
async function getLiveModelInfo(providerID: string, modelID: string) {
|
|
1700
1720
|
if (providerID !== 'opencode') return undefined;
|
|
1701
1721
|
return OpenCodeZen.getLiveFreeModelInfo(modelID);
|
|
@@ -2084,9 +2104,10 @@ export namespace Provider {
|
|
|
2084
2104
|
if (kiloUniqueModels.includes(modelID)) {
|
|
2085
2105
|
const kiloProvider = s.providers['kilo'];
|
|
2086
2106
|
if (kiloProvider && kiloProvider.info.models[modelID]) {
|
|
2087
|
-
|
|
2107
|
+
registryLog.info(() => ({
|
|
2088
2108
|
message: 'resolved short model name to kilo (unique)',
|
|
2089
|
-
modelID,
|
|
2109
|
+
name: modelID,
|
|
2110
|
+
candidate: `kilo/${modelID}`,
|
|
2090
2111
|
}));
|
|
2091
2112
|
return { providerID: 'kilo', modelID };
|
|
2092
2113
|
}
|
|
@@ -2095,9 +2116,10 @@ export namespace Provider {
|
|
|
2095
2116
|
if (modelID === 'formal-ai') {
|
|
2096
2117
|
const formalAiProvider = s.providers['formal-ai'];
|
|
2097
2118
|
if (formalAiProvider && formalAiProvider.info.models[modelID]) {
|
|
2098
|
-
|
|
2119
|
+
registryLog.info(() => ({
|
|
2099
2120
|
message: 'resolved short model name to formal-ai (canonical)',
|
|
2100
|
-
modelID,
|
|
2121
|
+
name: modelID,
|
|
2122
|
+
candidate: `formal-ai/${modelID}`,
|
|
2101
2123
|
}));
|
|
2102
2124
|
return { providerID: 'formal-ai', modelID };
|
|
2103
2125
|
}
|
|
@@ -2117,10 +2139,10 @@ export namespace Provider {
|
|
|
2117
2139
|
|
|
2118
2140
|
if (matchingProviders.length === 1) {
|
|
2119
2141
|
const providerID = matchingProviders[0];
|
|
2120
|
-
|
|
2142
|
+
registryLog.info(() => ({
|
|
2121
2143
|
message: 'resolved short model name (single match)',
|
|
2122
|
-
modelID,
|
|
2123
|
-
providerID
|
|
2144
|
+
name: modelID,
|
|
2145
|
+
candidate: `${providerID}/${modelID}`,
|
|
2124
2146
|
}));
|
|
2125
2147
|
return { providerID, modelID };
|
|
2126
2148
|
}
|
|
@@ -2128,9 +2150,10 @@ export namespace Provider {
|
|
|
2128
2150
|
// Multiple providers have this model - prefer OpenCode for shared free models
|
|
2129
2151
|
// This follows the convention that opencode is the primary free provider
|
|
2130
2152
|
if (matchingProviders.includes('opencode')) {
|
|
2131
|
-
|
|
2153
|
+
registryLog.info(() => ({
|
|
2132
2154
|
message: 'resolved short model name to opencode (multiple providers)',
|
|
2133
|
-
modelID,
|
|
2155
|
+
name: modelID,
|
|
2156
|
+
candidate: `opencode/${modelID}`,
|
|
2134
2157
|
availableProviders: matchingProviders,
|
|
2135
2158
|
}));
|
|
2136
2159
|
return { providerID: 'opencode', modelID };
|
|
@@ -2138,10 +2161,10 @@ export namespace Provider {
|
|
|
2138
2161
|
|
|
2139
2162
|
// Fallback to first matching provider
|
|
2140
2163
|
const providerID = matchingProviders[0];
|
|
2141
|
-
|
|
2164
|
+
registryLog.info(() => ({
|
|
2142
2165
|
message: 'resolved short model name (fallback)',
|
|
2143
|
-
modelID,
|
|
2144
|
-
providerID
|
|
2166
|
+
name: modelID,
|
|
2167
|
+
candidate: `${providerID}/${modelID}`,
|
|
2145
2168
|
availableProviders: matchingProviders,
|
|
2146
2169
|
}));
|
|
2147
2170
|
return { providerID, modelID };
|