@gajae-code/coding-agent 0.2.0 → 0.2.2
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/CHANGELOG.md +38 -1
- package/dist/types/cli/skills-cli.d.ts +9 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/skills.d.ts +26 -0
- package/dist/types/config/model-registry.d.ts +33 -4
- package/dist/types/config/models-config-schema.d.ts +52 -5
- package/dist/types/config/settings-schema.d.ts +1 -24
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- package/dist/types/modes/components/model-selector.d.ts +21 -1
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +17 -2
- package/src/cli/skills-cli.ts +88 -0
- package/src/cli.ts +7 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +6 -22
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/skills.ts +48 -0
- package/src/commands/state.ts +14 -4
- package/src/commands/team.ts +23 -3
- package/src/commit/agentic/index.ts +1 -0
- package/src/commit/pipeline.ts +1 -0
- package/src/config/model-registry.ts +269 -10
- package/src/config/models-config-schema.ts +124 -88
- package/src/config/settings-schema.ts +1 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
- package/src/defaults/gjc/skills/team/SKILL.md +29 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
- package/src/eval/py/prelude.py +1 -1
- package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +562 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +31 -12
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/main.ts +10 -1
- package/src/modes/components/model-selector.ts +109 -28
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +25 -1
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/selector-controller.ts +42 -2
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- package/src/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/sdk.ts +2 -1
- package/src/session/agent-session.ts +18 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/setup/provider-onboarding.ts +2 -0
- package/src/skill-state/active-state.ts +38 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/acp-builtins.ts +11 -2
- package/src/slash-commands/builtin-registry.ts +40 -13
- package/src/task/commands.ts +1 -5
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +2 -5
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- package/src/prompts/tools/update-goal.md +0 -3
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type Model,
|
|
13
13
|
type ModelManagerOptions,
|
|
14
14
|
type ModelRefreshStrategy,
|
|
15
|
+
type ModelRequestTransform,
|
|
15
16
|
openaiCodexModelManagerOptions,
|
|
16
17
|
PROVIDER_DESCRIPTORS,
|
|
17
18
|
readModelCache,
|
|
@@ -170,11 +171,60 @@ export function getRoleInfo(role: string, settings: Settings): RoleInfo {
|
|
|
170
171
|
|
|
171
172
|
type ProviderValidationMode = "models-config" | "runtime-register";
|
|
172
173
|
|
|
174
|
+
const OPENAI_REQUEST_TRANSFORM_APIS = new Set<Api>(["openai-completions", "openai-responses"]);
|
|
175
|
+
|
|
176
|
+
function getKnownProviderApis(providerName: string): Set<Api> {
|
|
177
|
+
const apis = new Set<Api>();
|
|
178
|
+
for (const model of getBundledModels(providerName as Parameters<typeof getBundledModels>[0])) {
|
|
179
|
+
apis.add((model as Model<Api>).api);
|
|
180
|
+
}
|
|
181
|
+
return apis;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isRequestTransformApi(api: Api): boolean {
|
|
185
|
+
return OPENAI_REQUEST_TRANSFORM_APIS.has(api);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function assertRequestTransformSupportedForKnownProvider(providerName: string, source: string): void {
|
|
189
|
+
const apis = getKnownProviderApis(providerName);
|
|
190
|
+
if (apis.size === 0) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Provider ${providerName}: ${source} requires an OpenAI-compatible "api" when the provider is not built in.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
for (const api of apis) {
|
|
196
|
+
if (!isRequestTransformApi(api)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Provider ${providerName}: ${source} is only supported with openai-completions or openai-responses APIs.`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function assertRequestTransformSupportedForModelApi(
|
|
205
|
+
providerName: string,
|
|
206
|
+
modelId: string,
|
|
207
|
+
api: Api,
|
|
208
|
+
source: string,
|
|
209
|
+
): void {
|
|
210
|
+
if (!isRequestTransformApi(api)) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Provider ${providerName}, model ${modelId}: ${source} is only supported with openai-completions or openai-responses APIs.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getKnownProviderModelApi(providerName: string, modelId: string): Api | undefined {
|
|
218
|
+
return getBundledModels(providerName as Parameters<typeof getBundledModels>[0]).find(model => model.id === modelId)
|
|
219
|
+
?.api as Api | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
173
222
|
interface ProviderValidationModel {
|
|
174
223
|
id: string;
|
|
175
224
|
api?: Api;
|
|
176
225
|
contextWindow?: number;
|
|
177
226
|
maxTokens?: number;
|
|
227
|
+
requestTransform?: ModelRequestTransform;
|
|
178
228
|
}
|
|
179
229
|
|
|
180
230
|
interface ProviderValidationConfig {
|
|
@@ -187,11 +237,16 @@ interface ProviderValidationConfig {
|
|
|
187
237
|
oauthConfigured?: boolean;
|
|
188
238
|
discovery?: ProviderDiscovery;
|
|
189
239
|
compat?: Model<Api>["compat"];
|
|
240
|
+
requestTransform?: ModelRequestTransform;
|
|
190
241
|
disableStrictTools?: boolean;
|
|
191
242
|
modelOverrides?: Record<string, unknown>;
|
|
192
243
|
models: ProviderValidationModel[];
|
|
193
244
|
}
|
|
194
245
|
|
|
246
|
+
function usesAwsCredentialChain(api: Api | undefined): boolean {
|
|
247
|
+
return api === "bedrock-converse-stream";
|
|
248
|
+
}
|
|
249
|
+
|
|
195
250
|
function validateProviderConfiguration(
|
|
196
251
|
providerName: string,
|
|
197
252
|
config: ProviderValidationConfig,
|
|
@@ -210,11 +265,12 @@ function validateProviderConfiguration(
|
|
|
210
265
|
!config.apiKey &&
|
|
211
266
|
!config.apiKeyEnv &&
|
|
212
267
|
!config.disableStrictTools &&
|
|
268
|
+
!config.requestTransform &&
|
|
213
269
|
!hasModelOverrides &&
|
|
214
270
|
!config.discovery
|
|
215
271
|
) {
|
|
216
272
|
throw new Error(
|
|
217
|
-
`Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
|
|
273
|
+
`Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "requestTransform", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
|
|
218
274
|
);
|
|
219
275
|
}
|
|
220
276
|
}
|
|
@@ -222,10 +278,14 @@ function validateProviderConfiguration(
|
|
|
222
278
|
if (!config.baseUrl) {
|
|
223
279
|
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
|
|
224
280
|
}
|
|
281
|
+
const usesProviderCredentialChain = usesAwsCredentialChain(config.api);
|
|
225
282
|
const requiresAuth =
|
|
226
283
|
mode === "runtime-register"
|
|
227
|
-
? !config.apiKey && !config.oauthConfigured
|
|
228
|
-
: !
|
|
284
|
+
? !usesProviderCredentialChain && !config.apiKey && !config.oauthConfigured
|
|
285
|
+
: !usesProviderCredentialChain &&
|
|
286
|
+
!config.apiKey &&
|
|
287
|
+
!config.apiKeyEnv &&
|
|
288
|
+
(config.auth ?? "apiKey") !== "none";
|
|
229
289
|
if (requiresAuth) {
|
|
230
290
|
throw new Error(
|
|
231
291
|
mode === "runtime-register"
|
|
@@ -238,6 +298,34 @@ function validateProviderConfiguration(
|
|
|
238
298
|
if (mode === "models-config" && config.discovery && !config.api) {
|
|
239
299
|
throw new Error(`Provider ${providerName}: "api" is required when discovery is enabled at provider level.`);
|
|
240
300
|
}
|
|
301
|
+
for (const [modelId, rawOverride] of Object.entries(config.modelOverrides ?? {})) {
|
|
302
|
+
const override = rawOverride as ModelOverride;
|
|
303
|
+
if (!override.requestTransform) continue;
|
|
304
|
+
const effectiveApi =
|
|
305
|
+
models.find(model => model.id === modelId)?.api ??
|
|
306
|
+
config.api ??
|
|
307
|
+
getKnownProviderModelApi(providerName, modelId);
|
|
308
|
+
if (effectiveApi) {
|
|
309
|
+
assertRequestTransformSupportedForModelApi(
|
|
310
|
+
providerName,
|
|
311
|
+
modelId,
|
|
312
|
+
effectiveApi,
|
|
313
|
+
'modelOverrides "requestTransform"',
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
assertRequestTransformSupportedForKnownProvider(providerName, 'modelOverrides "requestTransform"');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (config.requestTransform) {
|
|
320
|
+
if (config.api && !isRequestTransformApi(config.api)) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Provider ${providerName}: "requestTransform" is only supported with openai-completions or openai-responses APIs.`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (!config.api && models.length === 0) {
|
|
326
|
+
assertRequestTransformSupportedForKnownProvider(providerName, '"requestTransform"');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
241
329
|
|
|
242
330
|
for (const modelDef of models) {
|
|
243
331
|
if (!hasProviderApi && !modelDef.api) {
|
|
@@ -250,6 +338,23 @@ function validateProviderConfiguration(
|
|
|
250
338
|
if (!modelDef.id) {
|
|
251
339
|
throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
252
340
|
}
|
|
341
|
+
const effectiveApi = modelDef.api ?? config.api;
|
|
342
|
+
if (config.requestTransform && effectiveApi) {
|
|
343
|
+
assertRequestTransformSupportedForModelApi(
|
|
344
|
+
providerName,
|
|
345
|
+
modelDef.id,
|
|
346
|
+
effectiveApi,
|
|
347
|
+
'provider "requestTransform"',
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (modelDef.requestTransform && effectiveApi) {
|
|
351
|
+
assertRequestTransformSupportedForModelApi(
|
|
352
|
+
providerName,
|
|
353
|
+
modelDef.id,
|
|
354
|
+
effectiveApi,
|
|
355
|
+
'model "requestTransform"',
|
|
356
|
+
);
|
|
357
|
+
}
|
|
253
358
|
if (mode === "models-config") {
|
|
254
359
|
if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0) {
|
|
255
360
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
@@ -276,6 +381,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
276
381
|
auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
|
|
277
382
|
discovery: providerConfig.discovery as ProviderDiscovery | undefined,
|
|
278
383
|
compat: providerConfig.compat,
|
|
384
|
+
requestTransform: providerConfig.requestTransform,
|
|
279
385
|
disableStrictTools: providerConfig.disableStrictTools,
|
|
280
386
|
modelOverrides: providerConfig.modelOverrides,
|
|
281
387
|
models: (providerConfig.models ?? []) as ProviderValidationModel[],
|
|
@@ -294,6 +400,7 @@ interface ProviderOverride {
|
|
|
294
400
|
authHeader?: boolean;
|
|
295
401
|
compat?: Model<Api>["compat"];
|
|
296
402
|
transport?: Model<Api>["transport"];
|
|
403
|
+
requestTransform?: ModelRequestTransform;
|
|
297
404
|
}
|
|
298
405
|
|
|
299
406
|
const PROVIDER_BASE_URL_ENV_ALIASES: Record<string, readonly string[]> = {
|
|
@@ -341,13 +448,17 @@ function resolveProviderBaseUrlFromEnv(provider: string): string | undefined {
|
|
|
341
448
|
export function mergeDiscoveredModel<TApi extends Api>(
|
|
342
449
|
model: Model<TApi>,
|
|
343
450
|
existing: Model<Api> | undefined,
|
|
344
|
-
providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
|
|
451
|
+
providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport" | "requestTransform">,
|
|
345
452
|
): Model<TApi> {
|
|
346
453
|
if (existing) {
|
|
347
454
|
return {
|
|
348
455
|
...model,
|
|
349
456
|
baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
|
|
350
457
|
headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
|
|
458
|
+
requestTransform: mergeRequestTransform(
|
|
459
|
+
mergeRequestTransform(existing.requestTransform, model.requestTransform),
|
|
460
|
+
providerOverride?.requestTransform,
|
|
461
|
+
),
|
|
351
462
|
};
|
|
352
463
|
}
|
|
353
464
|
if (providerOverride) {
|
|
@@ -356,6 +467,7 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
356
467
|
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
357
468
|
headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
|
|
358
469
|
...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
|
|
470
|
+
requestTransform: mergeRequestTransform(model.requestTransform, providerOverride.requestTransform),
|
|
359
471
|
};
|
|
360
472
|
}
|
|
361
473
|
return model;
|
|
@@ -367,6 +479,7 @@ interface DiscoveryProviderConfig {
|
|
|
367
479
|
baseUrl?: string;
|
|
368
480
|
headers?: Record<string, string>;
|
|
369
481
|
compat?: Model<Api>["compat"];
|
|
482
|
+
requestTransform?: ModelRequestTransform;
|
|
370
483
|
discovery: ProviderDiscovery;
|
|
371
484
|
optional?: boolean;
|
|
372
485
|
}
|
|
@@ -397,6 +510,7 @@ interface CustomModelsResult {
|
|
|
397
510
|
discoverableProviders?: DiscoveryProviderConfig[];
|
|
398
511
|
configuredProviders?: Set<string>;
|
|
399
512
|
equivalence?: ModelEquivalenceConfig;
|
|
513
|
+
modelBindings?: NonNullable<ModelsConfig["modelBindings"]>;
|
|
400
514
|
error?: ConfigError;
|
|
401
515
|
found: boolean;
|
|
402
516
|
}
|
|
@@ -538,6 +652,24 @@ function mergeCompat<TBase extends object, TOverride extends object>(
|
|
|
538
652
|
return merged as TBase & TOverride;
|
|
539
653
|
}
|
|
540
654
|
|
|
655
|
+
function mergeRequestTransform(
|
|
656
|
+
base: ModelRequestTransform | undefined,
|
|
657
|
+
override: ModelRequestTransform | undefined,
|
|
658
|
+
): ModelRequestTransform | undefined {
|
|
659
|
+
if (!base) return override ? { ...override } : undefined;
|
|
660
|
+
if (!override) return { ...base };
|
|
661
|
+
return {
|
|
662
|
+
...base,
|
|
663
|
+
...override,
|
|
664
|
+
stripHeaders: override.stripHeaders ?? base.stripHeaders,
|
|
665
|
+
setHeaders: override.setHeaders ? { ...(base.setHeaders ?? {}), ...override.setHeaders } : base.setHeaders,
|
|
666
|
+
extraBody:
|
|
667
|
+
base.extraBody || override.extraBody
|
|
668
|
+
? { ...(base.extraBody ?? {}), ...(override.extraBody ?? {}) }
|
|
669
|
+
: undefined,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
541
673
|
function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {
|
|
542
674
|
const result = { ...model };
|
|
543
675
|
if (override.name !== undefined) result.name = override.name;
|
|
@@ -547,6 +679,8 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
547
679
|
if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
|
|
548
680
|
if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
|
|
549
681
|
if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
|
|
682
|
+
if (override.wireModelId !== undefined) result.wireModelId = override.wireModelId;
|
|
683
|
+
result.requestTransform = mergeRequestTransform(model.requestTransform, override.requestTransform);
|
|
550
684
|
if (override.premiumMultiplier !== undefined) result.premiumMultiplier = override.premiumMultiplier;
|
|
551
685
|
if (override.cost) {
|
|
552
686
|
result.cost = {
|
|
@@ -578,6 +712,8 @@ interface CustomModelDefinitionLike {
|
|
|
578
712
|
compat?: Model<Api>["compat"];
|
|
579
713
|
contextPromotionTarget?: string;
|
|
580
714
|
premiumMultiplier?: number;
|
|
715
|
+
wireModelId?: string;
|
|
716
|
+
requestTransform?: ModelRequestTransform;
|
|
581
717
|
}
|
|
582
718
|
|
|
583
719
|
interface CustomModelBuildOptions {
|
|
@@ -600,6 +736,8 @@ type CustomModelOverlay = {
|
|
|
600
736
|
compat?: Model<Api>["compat"];
|
|
601
737
|
contextPromotionTarget?: string;
|
|
602
738
|
premiumMultiplier?: number;
|
|
739
|
+
wireModelId?: string;
|
|
740
|
+
requestTransform?: ModelRequestTransform;
|
|
603
741
|
isOAuth?: boolean;
|
|
604
742
|
};
|
|
605
743
|
|
|
@@ -649,6 +787,7 @@ function buildCustomModelOverlay(
|
|
|
649
787
|
providerApiKey: string | undefined,
|
|
650
788
|
authHeader: boolean | undefined,
|
|
651
789
|
providerCompat: Model<Api>["compat"] | undefined,
|
|
790
|
+
providerRequestTransform: ModelRequestTransform | undefined,
|
|
652
791
|
providerAuth: ProviderAuthMode | undefined,
|
|
653
792
|
modelDef: CustomModelDefinitionLike,
|
|
654
793
|
): CustomModelOverlay | undefined {
|
|
@@ -668,6 +807,8 @@ function buildCustomModelOverlay(
|
|
|
668
807
|
maxTokens: modelDef.maxTokens,
|
|
669
808
|
headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
|
|
670
809
|
compat: mergeCompat(providerCompat, modelDef.compat),
|
|
810
|
+
requestTransform: mergeRequestTransform(providerRequestTransform, modelDef.requestTransform),
|
|
811
|
+
wireModelId: modelDef.wireModelId,
|
|
671
812
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
672
813
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
673
814
|
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
@@ -768,6 +909,8 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
768
909
|
headers: resolvedModel.headers,
|
|
769
910
|
compat: mergeCompat(reference?.compat, resolvedModel.compat),
|
|
770
911
|
contextPromotionTarget: resolvedModel.contextPromotionTarget,
|
|
912
|
+
wireModelId: resolvedModel.wireModelId,
|
|
913
|
+
requestTransform: resolvedModel.requestTransform,
|
|
771
914
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
772
915
|
isOAuth: resolvedModel.isOAuth,
|
|
773
916
|
} as Model<Api>);
|
|
@@ -810,6 +953,14 @@ export class ModelRegistry {
|
|
|
810
953
|
#providerOverrides: Map<string, ProviderOverride> = new Map();
|
|
811
954
|
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
812
955
|
#equivalenceConfig: ModelEquivalenceConfig | undefined;
|
|
956
|
+
#configuredModelBindings: NonNullable<ModelsConfig["modelBindings"]> | undefined;
|
|
957
|
+
#modelBindingsTargetSettings: Settings | undefined;
|
|
958
|
+
#appliedModelBindingRoles = new Set<string>();
|
|
959
|
+
#appliedAgentModelBindingOverrides = new Set<string>();
|
|
960
|
+
#modelBindingRoleBaselines = new Map<string, string | undefined>();
|
|
961
|
+
#agentModelBindingBaselines = new Map<string, string | undefined>();
|
|
962
|
+
#lastAppliedModelBindingRoles = new Map<string, string>();
|
|
963
|
+
#lastAppliedAgentModelBindingOverrides = new Map<string, string>();
|
|
813
964
|
#configError: ConfigError | undefined = undefined;
|
|
814
965
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
815
966
|
#lastStaticLoadMtime: number | null = null;
|
|
@@ -856,6 +1007,7 @@ export class ModelRegistry {
|
|
|
856
1007
|
this.#reloadStaticModels();
|
|
857
1008
|
this.#suppressedSelectors.clear();
|
|
858
1009
|
await this.#refreshRuntimeDiscoveries(strategy);
|
|
1010
|
+
this.#applyConfiguredModelBindingsToTarget();
|
|
859
1011
|
} finally {
|
|
860
1012
|
this.#resumeRebuild();
|
|
861
1013
|
}
|
|
@@ -889,6 +1041,7 @@ export class ModelRegistry {
|
|
|
889
1041
|
}
|
|
890
1042
|
}
|
|
891
1043
|
await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
|
|
1044
|
+
this.#applyConfiguredModelBindingsToTarget();
|
|
892
1045
|
} finally {
|
|
893
1046
|
this.#resumeRebuild();
|
|
894
1047
|
}
|
|
@@ -916,6 +1069,7 @@ export class ModelRegistry {
|
|
|
916
1069
|
this.#providerOverrides.clear();
|
|
917
1070
|
this.#modelOverrides.clear();
|
|
918
1071
|
this.#equivalenceConfig = undefined;
|
|
1072
|
+
this.#configuredModelBindings = undefined;
|
|
919
1073
|
this.#configError = undefined;
|
|
920
1074
|
this.#providerDiscoveryStates.clear();
|
|
921
1075
|
this.#loadModels();
|
|
@@ -938,6 +1092,7 @@ export class ModelRegistry {
|
|
|
938
1092
|
discoverableProviders = [],
|
|
939
1093
|
configuredProviders = new Set(),
|
|
940
1094
|
equivalence,
|
|
1095
|
+
modelBindings,
|
|
941
1096
|
error: configError,
|
|
942
1097
|
} = this.#loadCustomModels();
|
|
943
1098
|
this.#configError = configError;
|
|
@@ -947,6 +1102,7 @@ export class ModelRegistry {
|
|
|
947
1102
|
this.#providerOverrides = overrides;
|
|
948
1103
|
this.#modelOverrides = modelOverrides;
|
|
949
1104
|
this.#equivalenceConfig = equivalence;
|
|
1105
|
+
this.#configuredModelBindings = modelBindings;
|
|
950
1106
|
|
|
951
1107
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
952
1108
|
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
@@ -1044,6 +1200,8 @@ export class ModelRegistry {
|
|
|
1044
1200
|
headers: customModel.headers,
|
|
1045
1201
|
compat: customModel.compat,
|
|
1046
1202
|
contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
1203
|
+
wireModelId: customModel.wireModelId,
|
|
1204
|
+
requestTransform: customModel.requestTransform,
|
|
1047
1205
|
premiumMultiplier: customModel.premiumMultiplier ?? existingModel.premiumMultiplier,
|
|
1048
1206
|
} as Model<Api>);
|
|
1049
1207
|
} else {
|
|
@@ -1120,11 +1278,20 @@ export class ModelRegistry {
|
|
|
1120
1278
|
}
|
|
1121
1279
|
|
|
1122
1280
|
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1281
|
+
return models.map(model => {
|
|
1282
|
+
const normalized =
|
|
1283
|
+
providerConfig.provider === "ollama" &&
|
|
1284
|
+
providerConfig.api === "openai-responses" &&
|
|
1285
|
+
model.api === "openai-completions"
|
|
1286
|
+
? ({ ...model, api: "openai-responses" } as Model<Api>)
|
|
1287
|
+
: model;
|
|
1288
|
+
return {
|
|
1289
|
+
...normalized,
|
|
1290
|
+
requestTransform: providerConfig.requestTransform
|
|
1291
|
+
? mergeRequestTransform(undefined, providerConfig.requestTransform)
|
|
1292
|
+
: undefined,
|
|
1293
|
+
};
|
|
1294
|
+
});
|
|
1128
1295
|
}
|
|
1129
1296
|
|
|
1130
1297
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
@@ -1208,6 +1375,7 @@ export class ModelRegistry {
|
|
|
1208
1375
|
providerConfig.authHeader !== undefined ||
|
|
1209
1376
|
providerConfig.compat ||
|
|
1210
1377
|
providerConfig.disableStrictTools ||
|
|
1378
|
+
providerConfig.requestTransform ||
|
|
1211
1379
|
providerConfig.transport
|
|
1212
1380
|
) {
|
|
1213
1381
|
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
@@ -1218,6 +1386,7 @@ export class ModelRegistry {
|
|
|
1218
1386
|
authHeader: providerConfig.authHeader,
|
|
1219
1387
|
compat: mergeCompat(providerConfig.compat, disableStrictCompat),
|
|
1220
1388
|
transport: providerConfig.transport,
|
|
1389
|
+
requestTransform: providerConfig.requestTransform,
|
|
1221
1390
|
});
|
|
1222
1391
|
}
|
|
1223
1392
|
|
|
@@ -1233,6 +1402,7 @@ export class ModelRegistry {
|
|
|
1233
1402
|
baseUrl: providerConfig.baseUrl ?? resolveProviderBaseUrlFromEnv(providerName),
|
|
1234
1403
|
headers: providerConfig.headers,
|
|
1235
1404
|
compat: providerConfig.compat,
|
|
1405
|
+
requestTransform: providerConfig.requestTransform,
|
|
1236
1406
|
discovery: providerConfig.discovery,
|
|
1237
1407
|
optional: false,
|
|
1238
1408
|
});
|
|
@@ -1270,10 +1440,83 @@ export class ModelRegistry {
|
|
|
1270
1440
|
discoverableProviders,
|
|
1271
1441
|
configuredProviders,
|
|
1272
1442
|
equivalence: value.equivalence,
|
|
1443
|
+
modelBindings: value.modelBindings,
|
|
1273
1444
|
found: true,
|
|
1274
1445
|
};
|
|
1275
1446
|
}
|
|
1276
1447
|
|
|
1448
|
+
applyConfiguredModelBindings(targetSettings: Settings): void {
|
|
1449
|
+
this.#modelBindingsTargetSettings = targetSettings;
|
|
1450
|
+
this.#applyConfiguredModelBindingsToTarget();
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
#applyConfiguredModelBindingsToTarget(): void {
|
|
1454
|
+
const targetSettings = this.#modelBindingsTargetSettings;
|
|
1455
|
+
if (!targetSettings) return;
|
|
1456
|
+
const bindings = this.#configuredModelBindings;
|
|
1457
|
+
const nextModelRoles = { ...targetSettings.get("modelRoles") };
|
|
1458
|
+
const configuredModelRoles = bindings?.modelRoles ?? {};
|
|
1459
|
+
const configuredModelRoleKeys = new Set(Object.keys(configuredModelRoles));
|
|
1460
|
+
for (const role of this.#appliedModelBindingRoles) {
|
|
1461
|
+
if (configuredModelRoleKeys.has(role)) continue;
|
|
1462
|
+
const lastApplied = this.#lastAppliedModelBindingRoles.get(role);
|
|
1463
|
+
if (lastApplied !== undefined && nextModelRoles[role] === lastApplied) {
|
|
1464
|
+
const baseline = this.#modelBindingRoleBaselines.get(role);
|
|
1465
|
+
if (baseline === undefined) {
|
|
1466
|
+
delete nextModelRoles[role];
|
|
1467
|
+
} else {
|
|
1468
|
+
nextModelRoles[role] = baseline;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
this.#modelBindingRoleBaselines.delete(role);
|
|
1472
|
+
this.#lastAppliedModelBindingRoles.delete(role);
|
|
1473
|
+
}
|
|
1474
|
+
for (const [role, modelId] of Object.entries(configuredModelRoles)) {
|
|
1475
|
+
if (!modelId) continue;
|
|
1476
|
+
const previousApplied = this.#lastAppliedModelBindingRoles.get(role);
|
|
1477
|
+
if (!this.#modelBindingRoleBaselines.has(role)) {
|
|
1478
|
+
this.#modelBindingRoleBaselines.set(role, nextModelRoles[role]);
|
|
1479
|
+
}
|
|
1480
|
+
if (previousApplied === undefined || nextModelRoles[role] === previousApplied) {
|
|
1481
|
+
nextModelRoles[role] = modelId;
|
|
1482
|
+
this.#lastAppliedModelBindingRoles.set(role, modelId);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
targetSettings.override("modelRoles", nextModelRoles);
|
|
1486
|
+
this.#appliedModelBindingRoles = new Set(Object.keys(configuredModelRoles));
|
|
1487
|
+
|
|
1488
|
+
const nextAgentModelOverrides = { ...targetSettings.get("task.agentModelOverrides") };
|
|
1489
|
+
const configuredAgentModelOverrides = bindings?.agentModelOverrides ?? {};
|
|
1490
|
+
const configuredAgentModelOverrideKeys = new Set(Object.keys(configuredAgentModelOverrides));
|
|
1491
|
+
for (const agentName of this.#appliedAgentModelBindingOverrides) {
|
|
1492
|
+
if (configuredAgentModelOverrideKeys.has(agentName)) continue;
|
|
1493
|
+
const lastApplied = this.#lastAppliedAgentModelBindingOverrides.get(agentName);
|
|
1494
|
+
if (lastApplied !== undefined && nextAgentModelOverrides[agentName] === lastApplied) {
|
|
1495
|
+
const baseline = this.#agentModelBindingBaselines.get(agentName);
|
|
1496
|
+
if (baseline === undefined) {
|
|
1497
|
+
delete nextAgentModelOverrides[agentName];
|
|
1498
|
+
} else {
|
|
1499
|
+
nextAgentModelOverrides[agentName] = baseline;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
this.#agentModelBindingBaselines.delete(agentName);
|
|
1503
|
+
this.#lastAppliedAgentModelBindingOverrides.delete(agentName);
|
|
1504
|
+
}
|
|
1505
|
+
for (const [agentName, modelId] of Object.entries(configuredAgentModelOverrides)) {
|
|
1506
|
+
if (!modelId) continue;
|
|
1507
|
+
const previousApplied = this.#lastAppliedAgentModelBindingOverrides.get(agentName);
|
|
1508
|
+
if (!this.#agentModelBindingBaselines.has(agentName)) {
|
|
1509
|
+
this.#agentModelBindingBaselines.set(agentName, nextAgentModelOverrides[agentName]);
|
|
1510
|
+
}
|
|
1511
|
+
if (previousApplied === undefined || nextAgentModelOverrides[agentName] === previousApplied) {
|
|
1512
|
+
nextAgentModelOverrides[agentName] = modelId;
|
|
1513
|
+
this.#lastAppliedAgentModelBindingOverrides.set(agentName, modelId);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
targetSettings.override("task.agentModelOverrides", nextAgentModelOverrides);
|
|
1517
|
+
this.#appliedAgentModelBindingOverrides = new Set(Object.keys(configuredAgentModelOverrides));
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1277
1520
|
async #refreshRuntimeDiscoveries(
|
|
1278
1521
|
strategy: ModelRefreshStrategy,
|
|
1279
1522
|
providerFilter?: ReadonlySet<string>,
|
|
@@ -1838,11 +2081,15 @@ export class ModelRegistry {
|
|
|
1838
2081
|
headers: override.headers ? { ...(baseOverride?.headers ?? {}), ...override.headers } : baseOverride?.headers,
|
|
1839
2082
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
1840
2083
|
transport: override.transport ?? baseOverride?.transport,
|
|
2084
|
+
requestTransform: mergeRequestTransform(baseOverride?.requestTransform, override.requestTransform),
|
|
1841
2085
|
};
|
|
1842
2086
|
}
|
|
1843
2087
|
#applyProviderTransportOverride<T extends { baseUrl?: string; headers?: Record<string, string> }>(
|
|
1844
2088
|
entry: T,
|
|
1845
|
-
override: Pick<
|
|
2089
|
+
override: Pick<
|
|
2090
|
+
ProviderOverride,
|
|
2091
|
+
"baseUrl" | "headers" | "authHeader" | "apiKey" | "transport" | "requestTransform"
|
|
2092
|
+
>,
|
|
1846
2093
|
): T {
|
|
1847
2094
|
const headers = mergeAuthHeader(
|
|
1848
2095
|
override.headers ? { ...entry.headers, ...override.headers } : entry.headers,
|
|
@@ -1856,6 +2103,10 @@ export class ModelRegistry {
|
|
|
1856
2103
|
// Preserve the model's existing transport when the override omits one;
|
|
1857
2104
|
// providers without a `transport` field keep the default per-API dispatch.
|
|
1858
2105
|
...(override.transport !== undefined ? { transport: override.transport } : {}),
|
|
2106
|
+
requestTransform: mergeRequestTransform(
|
|
2107
|
+
(entry as { requestTransform?: ModelRequestTransform }).requestTransform,
|
|
2108
|
+
override.requestTransform,
|
|
2109
|
+
),
|
|
1859
2110
|
};
|
|
1860
2111
|
}
|
|
1861
2112
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
@@ -1942,6 +2193,7 @@ export class ModelRegistry {
|
|
|
1942
2193
|
providerConfig.apiKeyEnv ? resolveApiKeyEnvConfig(providerConfig.apiKeyEnv) : providerConfig.apiKey,
|
|
1943
2194
|
providerConfig.authHeader,
|
|
1944
2195
|
providerCompat,
|
|
2196
|
+
providerConfig.requestTransform,
|
|
1945
2197
|
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
1946
2198
|
modelDef as CustomModelDefinitionLike,
|
|
1947
2199
|
);
|
|
@@ -2239,6 +2491,7 @@ export class ModelRegistry {
|
|
|
2239
2491
|
apiKey: config.apiKey,
|
|
2240
2492
|
api: config.api,
|
|
2241
2493
|
oauthConfigured: Boolean(config.oauth),
|
|
2494
|
+
requestTransform: config.requestTransform,
|
|
2242
2495
|
models: (config.models ?? []) as ProviderValidationModel[],
|
|
2243
2496
|
},
|
|
2244
2497
|
"runtime-register",
|
|
@@ -2302,6 +2555,7 @@ export class ModelRegistry {
|
|
|
2302
2555
|
config.apiKey,
|
|
2303
2556
|
config.authHeader,
|
|
2304
2557
|
config.compat,
|
|
2558
|
+
config.requestTransform,
|
|
2305
2559
|
undefined,
|
|
2306
2560
|
modelDef as CustomModelDefinitionLike,
|
|
2307
2561
|
);
|
|
@@ -2346,6 +2600,7 @@ export class ModelRegistry {
|
|
|
2346
2600
|
config.headers ||
|
|
2347
2601
|
config.apiKey ||
|
|
2348
2602
|
config.authHeader !== undefined ||
|
|
2603
|
+
config.requestTransform !== undefined ||
|
|
2349
2604
|
config.transport !== undefined
|
|
2350
2605
|
) {
|
|
2351
2606
|
const transportOverride = {
|
|
@@ -2353,6 +2608,7 @@ export class ModelRegistry {
|
|
|
2353
2608
|
headers: config.headers,
|
|
2354
2609
|
apiKey: config.apiKey,
|
|
2355
2610
|
authHeader: config.authHeader,
|
|
2611
|
+
requestTransform: config.requestTransform,
|
|
2356
2612
|
transport: config.transport,
|
|
2357
2613
|
};
|
|
2358
2614
|
const nextRuntimeOverride = this.#mergeProviderOverride(
|
|
@@ -2400,6 +2656,7 @@ export interface ProviderConfigInput {
|
|
|
2400
2656
|
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
2401
2657
|
headers?: Record<string, string>;
|
|
2402
2658
|
compat?: Model<Api>["compat"];
|
|
2659
|
+
requestTransform?: ModelRequestTransform;
|
|
2403
2660
|
authHeader?: boolean;
|
|
2404
2661
|
/** Streaming transport override — see {@link Model.transport}. */
|
|
2405
2662
|
transport?: Model<Api>["transport"];
|
|
@@ -2423,6 +2680,8 @@ export interface ProviderConfigInput {
|
|
|
2423
2680
|
maxTokens: number;
|
|
2424
2681
|
headers?: Record<string, string>;
|
|
2425
2682
|
compat?: Model<Api>["compat"];
|
|
2683
|
+
requestTransform?: ModelRequestTransform;
|
|
2684
|
+
wireModelId?: string;
|
|
2426
2685
|
contextPromotionTarget?: string;
|
|
2427
2686
|
premiumMultiplier?: number;
|
|
2428
2687
|
}>;
|