portable-agent-layer 0.85.22 → 0.85.24
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/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +11 -0
- package/package.json +1 -1
- package/src/cli/doctor/inference.ts +47 -4
- package/src/cli/index.ts +1 -1
- package/src/hooks/handlers/agenda.ts +4 -4
- package/src/hooks/lib/inference.ts +60 -43
- package/src/hooks/lib/models.ts +34 -0
- package/src/hooks/lib/opencode-config.ts +16 -5
- package/src/tools/self-model.ts +1 -1
|
@@ -425,6 +425,17 @@ LoadContext.ts
|
|
|
425
425
|
8. Other recent learnings (other projects, titles only)
|
|
426
426
|
```
|
|
427
427
|
|
|
428
|
+
### Injection Budget
|
|
429
|
+
|
|
430
|
+
Claude Code caps each string a hook injects at **10,000 characters**: plain stdout as a whole, or each JSON field (`additionalContext`, `systemMessage`) on its own. Over the cap, the text is saved to a file and replaced with the file path plus a preview of the first 2,000 characters, and the model is never told to read the file. No setting raises the cap.
|
|
431
|
+
|
|
432
|
+
Design rules for anything injected by a hook (Tier 3 and per-prompt reminders):
|
|
433
|
+
- Stay under 10,000 characters per hook output.
|
|
434
|
+
- Put what matters most in the first 2,000 characters, in case a future change crosses the cap.
|
|
435
|
+
- Measure before adding a section: pipe a SessionStart payload into `LoadContext.ts` and count the bytes.
|
|
436
|
+
|
|
437
|
+
Tier 1 and Tier 2 load as memory files, not hook output, so this cap does not apply to them. Other agents' limits are not documented here; check them before relying on the same budget.
|
|
438
|
+
|
|
428
439
|
### On-Demand Context
|
|
429
440
|
|
|
430
441
|
Everything else loads via the routing table in CLAUDE.md. The AI reads files only when the current task requires that context — no upfront loading of the full system.
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { resolve } from "node:path";
|
|
2
3
|
import { previewInferenceRoute } from "../../hooks/lib/inference";
|
|
3
4
|
import { opencodeBackgroundModel } from "../../hooks/lib/opencode-config";
|
|
@@ -51,8 +52,13 @@ export function apiKeyFindings(env: Env): Finding[] {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
type Preview = ReturnType<typeof previewInferenceRoute>;
|
|
56
|
+
|
|
57
|
+
function modelSuffix(preview: Preview): string {
|
|
58
|
+
return preview.model ? ` on ${preview.model}` : "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function routeFinding(preview: Preview = previewInferenceRoute()): Finding | null {
|
|
56
62
|
if (preview.route === "disabled") return null;
|
|
57
63
|
if (preview.route === "none")
|
|
58
64
|
return failing(
|
|
@@ -63,10 +69,13 @@ function routeFinding(): Finding | null {
|
|
|
63
69
|
if (preview.route.endsWith("-api"))
|
|
64
70
|
return warning(
|
|
65
71
|
"inference.api",
|
|
66
|
-
`Background inference goes through ${preview.route} (${preview.reason}) — it bills the API key`,
|
|
72
|
+
`Background inference goes through ${preview.route}${modelSuffix(preview)} (${preview.reason}) — it bills the API key`,
|
|
67
73
|
{ say: "Put the agent's CLI on PATH to use your subscription instead" }
|
|
68
74
|
);
|
|
69
|
-
return passed(
|
|
75
|
+
return passed(
|
|
76
|
+
"inference",
|
|
77
|
+
`Inference: ${preview.route}${modelSuffix(preview)} (${preview.reason})`
|
|
78
|
+
);
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function opencodeModelFinding(): Finding {
|
|
@@ -81,11 +90,45 @@ function opencodeModelFinding(): Finding {
|
|
|
81
90
|
);
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
function aboutField(about: string, field: string): string | null {
|
|
94
|
+
const line = about.split("\n").find((l) => l.startsWith(`${field} `));
|
|
95
|
+
return line ? line.slice(field.length).trim() || null : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Cursor's free plan refuses every named model, so a pinned one fails all background inference. */
|
|
99
|
+
export function cursorPlanFinding(about: string): Finding | null {
|
|
100
|
+
const model = aboutField(about, "Model");
|
|
101
|
+
const tier = aboutField(about, "Subscription Tier");
|
|
102
|
+
if (!model || !tier) return null;
|
|
103
|
+
if (tier === "Free" && model !== "Auto")
|
|
104
|
+
return warning(
|
|
105
|
+
"cursor.model",
|
|
106
|
+
`Cursor is set to ${model}, but its free plan only runs Auto — background inference fails`,
|
|
107
|
+
{ say: "Switch Cursor's model to Auto, or upgrade the plan" }
|
|
108
|
+
);
|
|
109
|
+
return passed("cursor.model", `Cursor model ${model} runs on the ${tier} plan`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function cursorAbout(): string {
|
|
113
|
+
const result = spawnSync("cursor-agent", ["about"], {
|
|
114
|
+
encoding: "utf-8",
|
|
115
|
+
shell: true,
|
|
116
|
+
timeout: 15_000,
|
|
117
|
+
});
|
|
118
|
+
return result.status === 0 ? result.stdout : "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function cursorFindings(): Finding[] {
|
|
122
|
+
const finding = cursorPlanFinding(cursorAbout());
|
|
123
|
+
return finding ? [finding] : [];
|
|
124
|
+
}
|
|
125
|
+
|
|
84
126
|
export function inferenceFindings(agents: AgentName[]): Finding[] {
|
|
85
127
|
const route = routeFinding();
|
|
86
128
|
return [
|
|
87
129
|
...(route ? [route] : []),
|
|
88
130
|
...(agents.includes("opencode") ? [opencodeModelFinding()] : []),
|
|
131
|
+
...(agents.includes("cursor") ? cursorFindings() : []),
|
|
89
132
|
...leakedEnvFindings(process.env, process.platform),
|
|
90
133
|
...apiKeyFindings(process.env),
|
|
91
134
|
];
|
package/src/cli/index.ts
CHANGED
|
@@ -457,7 +457,7 @@ async function probeInference(): Promise<void> {
|
|
|
457
457
|
);
|
|
458
458
|
} else {
|
|
459
459
|
console.log(
|
|
460
|
-
` ${red}✗${reset} ${tag} ${String(elapsedMs).padStart(6)}ms ${dim}failed — see ~/.pal/debug/debug.log${reset}`
|
|
460
|
+
` ${red}✗${reset} ${tag} ${String(elapsedMs).padStart(6)}ms ${dim}${r.error ?? "failed — see ~/.pal/debug/debug.log"}${reset}`
|
|
461
461
|
);
|
|
462
462
|
}
|
|
463
463
|
}
|
|
@@ -178,7 +178,7 @@ async function guessMissingServes(sessionId?: string): Promise<number> {
|
|
|
178
178
|
maxTokens: 700,
|
|
179
179
|
timeout: 90000,
|
|
180
180
|
jsonSchema: SERVES_SCHEMA,
|
|
181
|
-
|
|
181
|
+
tier: "medium",
|
|
182
182
|
caller: "agenda-serves",
|
|
183
183
|
sessionId,
|
|
184
184
|
});
|
|
@@ -252,7 +252,7 @@ async function refreshGoalLinks(
|
|
|
252
252
|
maxTokens: 600,
|
|
253
253
|
timeout: 90000,
|
|
254
254
|
jsonSchema: LINKS_SCHEMA,
|
|
255
|
-
|
|
255
|
+
tier: "medium",
|
|
256
256
|
caller: "agenda-goal-links",
|
|
257
257
|
sessionId,
|
|
258
258
|
});
|
|
@@ -306,7 +306,7 @@ async function extractWaitingOn(sessionId?: string): Promise<number> {
|
|
|
306
306
|
maxTokens: 120,
|
|
307
307
|
timeout: 60000,
|
|
308
308
|
jsonSchema: WAITING_SCHEMA,
|
|
309
|
-
|
|
309
|
+
tier: "medium",
|
|
310
310
|
caller: "agenda-waiting-on",
|
|
311
311
|
sessionId,
|
|
312
312
|
});
|
|
@@ -352,7 +352,7 @@ async function writeMoves(sessionId?: string): Promise<boolean> {
|
|
|
352
352
|
maxTokens: 400,
|
|
353
353
|
timeout: 90000,
|
|
354
354
|
jsonSchema: MOVES_SCHEMA,
|
|
355
|
-
|
|
355
|
+
tier: "medium",
|
|
356
356
|
caller: "agenda-moves",
|
|
357
357
|
sessionId,
|
|
358
358
|
});
|
|
@@ -32,8 +32,13 @@ import {
|
|
|
32
32
|
isOpencode,
|
|
33
33
|
} from "./agent";
|
|
34
34
|
import { logDebug, logError } from "./log";
|
|
35
|
-
import {
|
|
36
|
-
|
|
35
|
+
import {
|
|
36
|
+
type FixedModelRoute,
|
|
37
|
+
type InferenceTier,
|
|
38
|
+
inferenceModel,
|
|
39
|
+
isFixedModelRoute,
|
|
40
|
+
} from "./models";
|
|
41
|
+
import { opencodeTierModel, writeInstructionFreeConfig } from "./opencode-config";
|
|
37
42
|
import { buildSpawnGuardEnv, getInferenceDepth, SPAWN_GUARD_ENV } from "./spawn-guard";
|
|
38
43
|
import { findBinaryOnPath } from "./which";
|
|
39
44
|
|
|
@@ -45,24 +50,39 @@ export function hasOpenAiKey(): boolean {
|
|
|
45
50
|
return !!process.env.PAL_OPENAI_API_KEY;
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
type InferenceRoute =
|
|
54
|
+
| FixedModelRoute
|
|
55
|
+
| "opencode-spawn"
|
|
56
|
+
| "copilot-spawn"
|
|
57
|
+
| "cursor-spawn"
|
|
58
|
+
| "disabled"
|
|
59
|
+
| "none";
|
|
60
|
+
|
|
61
|
+
interface RoutePreview {
|
|
62
|
+
agent: string;
|
|
63
|
+
route: InferenceRoute;
|
|
64
|
+
reason: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function modelFor(route: FixedModelRoute, opts: InferenceOptions): string {
|
|
68
|
+
return inferenceModel(route, opts.tier);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function smallModelOf(route: InferenceRoute): string | undefined {
|
|
72
|
+
if (route === "opencode-spawn") return opencodeTierModel("small") ?? undefined;
|
|
73
|
+
return isFixedModelRoute(route) ? inferenceModel(route, "small") : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
48
76
|
/**
|
|
49
77
|
* Preview what `inference()` would do RIGHT NOW given current env + binaries.
|
|
50
78
|
* Pure diagnostic — never spawns or fetches. Used by `pal cli doctor`.
|
|
51
79
|
*/
|
|
52
|
-
export function previewInferenceRoute(): {
|
|
53
|
-
|
|
54
|
-
route
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| "opencode-spawn"
|
|
59
|
-
| "copilot-spawn"
|
|
60
|
-
| "cursor-spawn"
|
|
61
|
-
| "anthropic-api"
|
|
62
|
-
| "disabled"
|
|
63
|
-
| "none";
|
|
64
|
-
reason: string;
|
|
65
|
-
} {
|
|
80
|
+
export function previewInferenceRoute(): RoutePreview & { model?: string } {
|
|
81
|
+
const preview = previewRoute();
|
|
82
|
+
return { ...preview, model: smallModelOf(preview.route) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function previewRoute(): RoutePreview {
|
|
66
86
|
const agent = getActiveAgent();
|
|
67
87
|
if (process.env.PAL_INFERENCE_DISABLED === "1") {
|
|
68
88
|
return {
|
|
@@ -115,7 +135,8 @@ export function canInfer(): boolean {
|
|
|
115
135
|
interface InferenceOptions {
|
|
116
136
|
system?: string;
|
|
117
137
|
user: string;
|
|
118
|
-
model
|
|
138
|
+
/** Model size; the active route maps it to that provider's model. Default "small". */
|
|
139
|
+
tier?: InferenceTier;
|
|
119
140
|
maxTokens?: number;
|
|
120
141
|
timeout?: number;
|
|
121
142
|
/** JSON schema for structured output — guarantees valid JSON matching the schema */
|
|
@@ -129,6 +150,8 @@ interface InferenceOptions {
|
|
|
129
150
|
interface InferenceResult {
|
|
130
151
|
success: boolean;
|
|
131
152
|
output?: string;
|
|
153
|
+
/** First line of a failed CLI's error output. */
|
|
154
|
+
error?: string;
|
|
132
155
|
usage?: { inputTokens: number; outputTokens: number };
|
|
133
156
|
}
|
|
134
157
|
|
|
@@ -152,7 +175,7 @@ export async function inference(opts: InferenceOptions): Promise<InferenceResult
|
|
|
152
175
|
if (bin) {
|
|
153
176
|
logDebug(
|
|
154
177
|
"inference",
|
|
155
|
-
`${tag} route=claude-spawn agent=${agent} model=${opts
|
|
178
|
+
`${tag} route=claude-spawn agent=${agent} model=${modelFor("claude-spawn", opts)}`
|
|
156
179
|
);
|
|
157
180
|
return inferenceViaClaudeSpawn(bin, opts);
|
|
158
181
|
}
|
|
@@ -269,7 +292,7 @@ export function buildClaudeArgs(
|
|
|
269
292
|
const args = [
|
|
270
293
|
"--print",
|
|
271
294
|
"--model",
|
|
272
|
-
opts
|
|
295
|
+
modelFor("claude-spawn", opts),
|
|
273
296
|
"--tools",
|
|
274
297
|
"",
|
|
275
298
|
"--output-format",
|
|
@@ -327,9 +350,11 @@ async function inferenceViaClaudeSpawn(
|
|
|
327
350
|
* given, so this costs nothing on POSIX and is the only thing that works on
|
|
328
351
|
* Windows. Do not move the prompt back into argv.
|
|
329
352
|
*/
|
|
330
|
-
export function buildCodexArgs(
|
|
353
|
+
export function buildCodexArgs(opts: InferenceOptions): string[] {
|
|
331
354
|
return [
|
|
332
355
|
"exec",
|
|
356
|
+
"-m",
|
|
357
|
+
modelFor("codex-spawn", opts),
|
|
333
358
|
"--color",
|
|
334
359
|
"never",
|
|
335
360
|
"--skip-git-repo-check",
|
|
@@ -357,8 +382,9 @@ export function buildCodexArgs(_opts: InferenceOptions): string[] {
|
|
|
357
382
|
* element does not survive cmd.exe on Windows. Provider/model is left unset so
|
|
358
383
|
* opencode uses the user's configured default.
|
|
359
384
|
*/
|
|
360
|
-
export function buildOpencodeArgs(
|
|
361
|
-
|
|
385
|
+
export function buildOpencodeArgs(opts: InferenceOptions): string[] {
|
|
386
|
+
const model = opencodeTierModel(opts.tier ?? "small");
|
|
387
|
+
return ["run", "--pure", "--format", "json", ...(model ? ["-m", model] : [])];
|
|
362
388
|
}
|
|
363
389
|
|
|
364
390
|
// Without its own AGENTS.md, opencode falls back to ~/.claude/CLAUDE.md, and it
|
|
@@ -520,6 +546,13 @@ export function parseJsonFromOutput(output: string): unknown | null {
|
|
|
520
546
|
return null;
|
|
521
547
|
}
|
|
522
548
|
|
|
549
|
+
function firstLine(text: string): string | undefined {
|
|
550
|
+
return text
|
|
551
|
+
.split("\n")
|
|
552
|
+
.find((line) => line.trim())
|
|
553
|
+
?.trim();
|
|
554
|
+
}
|
|
555
|
+
|
|
523
556
|
interface RawSpawnResult {
|
|
524
557
|
code: number | null;
|
|
525
558
|
stdout: string;
|
|
@@ -685,7 +718,7 @@ async function inferenceViaCliSpawn(
|
|
|
685
718
|
"inference:spawn",
|
|
686
719
|
`${tag} exited=${attempt.code} binary=${binaryName} argv=${JSON.stringify(args)} stderr(${attempt.stderr.length})=${attempt.stderr.slice(0, 300)} stdout(${attempt.stdout.length})=${attempt.stdout.slice(0, 300)}`
|
|
687
720
|
);
|
|
688
|
-
return finish({ success: false });
|
|
721
|
+
return finish({ success: false, error: firstLine(attempt.stderr || attempt.stdout) });
|
|
689
722
|
}
|
|
690
723
|
const rawText = attempt.stdout.trim();
|
|
691
724
|
if (!rawText) return finish({ success: false });
|
|
@@ -716,21 +749,14 @@ async function inferenceViaApi(opts: InferenceOptions): Promise<InferenceResult>
|
|
|
716
749
|
const apiKey = process.env.PAL_ANTHROPIC_API_KEY;
|
|
717
750
|
if (!apiKey) return { success: false };
|
|
718
751
|
|
|
719
|
-
const {
|
|
720
|
-
system,
|
|
721
|
-
user,
|
|
722
|
-
model = HAIKU_MODEL,
|
|
723
|
-
maxTokens = 200,
|
|
724
|
-
timeout = 5000,
|
|
725
|
-
jsonSchema,
|
|
726
|
-
} = opts;
|
|
752
|
+
const { system, user, maxTokens = 200, timeout = 5000, jsonSchema } = opts;
|
|
727
753
|
|
|
728
754
|
try {
|
|
729
755
|
const controller = new AbortController();
|
|
730
756
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
731
757
|
|
|
732
758
|
const body: Record<string, unknown> = {
|
|
733
|
-
model,
|
|
759
|
+
model: modelFor("anthropic-api", opts),
|
|
734
760
|
max_tokens: maxTokens,
|
|
735
761
|
messages: [{ role: "user", content: user }],
|
|
736
762
|
};
|
|
@@ -787,20 +813,11 @@ async function inferenceViaApi(opts: InferenceOptions): Promise<InferenceResult>
|
|
|
787
813
|
// structured-output schema for JSON-mode callers.
|
|
788
814
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
789
815
|
|
|
790
|
-
const OPENAI_DEFAULT_MODEL = "gpt-5.4-mini";
|
|
791
|
-
|
|
792
816
|
async function inferenceViaOpenAiApi(opts: InferenceOptions): Promise<InferenceResult> {
|
|
793
817
|
const apiKey = process.env.PAL_OPENAI_API_KEY;
|
|
794
818
|
if (!apiKey) return { success: false };
|
|
795
819
|
|
|
796
|
-
const {
|
|
797
|
-
system,
|
|
798
|
-
user,
|
|
799
|
-
model = OPENAI_DEFAULT_MODEL,
|
|
800
|
-
maxTokens = 500,
|
|
801
|
-
timeout = 15000,
|
|
802
|
-
jsonSchema,
|
|
803
|
-
} = opts;
|
|
820
|
+
const { system, user, maxTokens = 500, timeout = 15000, jsonSchema } = opts;
|
|
804
821
|
|
|
805
822
|
try {
|
|
806
823
|
const controller = new AbortController();
|
|
@@ -811,7 +828,7 @@ async function inferenceViaOpenAiApi(opts: InferenceOptions): Promise<InferenceR
|
|
|
811
828
|
messages.push({ role: "user", content: user });
|
|
812
829
|
|
|
813
830
|
const body: Record<string, unknown> = {
|
|
814
|
-
model,
|
|
831
|
+
model: modelFor("openai-api", opts),
|
|
815
832
|
max_tokens: maxTokens,
|
|
816
833
|
messages,
|
|
817
834
|
};
|
package/src/hooks/lib/models.ts
CHANGED
|
@@ -8,6 +8,40 @@ export const HAIKU_MODEL = "claude-haiku-4-5-20251001";
|
|
|
8
8
|
export const SONNET_MODEL = "claude-sonnet-5";
|
|
9
9
|
export const FABLE_MODEL = "claude-fable-5";
|
|
10
10
|
|
|
11
|
+
export type InferenceTier = "small" | "medium";
|
|
12
|
+
|
|
13
|
+
export type FixedModelRoute =
|
|
14
|
+
| "claude-spawn"
|
|
15
|
+
| "anthropic-api"
|
|
16
|
+
| "codex-spawn"
|
|
17
|
+
| "openai-api";
|
|
18
|
+
|
|
19
|
+
const ANTHROPIC_MODELS = { small: HAIKU_MODEL, medium: SONNET_MODEL };
|
|
20
|
+
const OPENAI_MODELS = { small: "gpt-6-luna", medium: "gpt-6-sol" };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* opencode's models come from the user's own config (opencodeTierModel). Copilot and
|
|
24
|
+
* Cursor are absent on purpose: which named models they accept depends on the user's
|
|
25
|
+
* plan, and a free plan refuses every one but Auto.
|
|
26
|
+
*/
|
|
27
|
+
const INFERENCE_MODELS: Record<FixedModelRoute, Record<InferenceTier, string>> = {
|
|
28
|
+
"claude-spawn": ANTHROPIC_MODELS,
|
|
29
|
+
"anthropic-api": ANTHROPIC_MODELS,
|
|
30
|
+
"codex-spawn": OPENAI_MODELS,
|
|
31
|
+
"openai-api": OPENAI_MODELS,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function isFixedModelRoute(route: string): route is FixedModelRoute {
|
|
35
|
+
return Object.hasOwn(INFERENCE_MODELS, route);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function inferenceModel(
|
|
39
|
+
route: FixedModelRoute,
|
|
40
|
+
tier: InferenceTier = "small"
|
|
41
|
+
): string {
|
|
42
|
+
return INFERENCE_MODELS[route][tier];
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
/**
|
|
12
46
|
* Per-agent flagship model used to AUTHOR new skills (via `create-skill`).
|
|
13
47
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import type { InferenceTier } from "./models";
|
|
3
4
|
import { platform } from "./paths";
|
|
4
5
|
|
|
5
6
|
const GLOBAL_CONFIG_FILES = ["config.json", "opencode.json", "opencode.jsonc"];
|
|
@@ -17,19 +18,29 @@ function readConfig(name: string): OpencodeConfig | null {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function pinnedModel(config: OpencodeConfig | null): string | null {
|
|
21
|
-
|
|
21
|
+
function pinnedModel(config: OpencodeConfig | null, key = "model"): string | null {
|
|
22
|
+
const value = config?.[key];
|
|
23
|
+
return typeof value === "string" && value ? value : null;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
export function opencodeBackgroundModel(): string | null {
|
|
26
|
+
function firstPinned(key: string): string | null {
|
|
26
27
|
for (const name of GLOBAL_CONFIG_FILES) {
|
|
27
|
-
const model = pinnedModel(readConfig(name));
|
|
28
|
+
const model = pinnedModel(readConfig(name), key);
|
|
28
29
|
if (model) return model;
|
|
29
30
|
}
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/** Without a pinned model, `opencode run` falls back to whatever was last picked in the TUI. */
|
|
35
|
+
export function opencodeBackgroundModel(): string | null {
|
|
36
|
+
return firstPinned("model");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function opencodeTierModel(tier: InferenceTier): string | null {
|
|
40
|
+
const small = tier === "small" ? firstPinned("small_model") : null;
|
|
41
|
+
return small ?? opencodeBackgroundModel();
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
function withoutInstructions(config: OpencodeConfig): OpencodeConfig {
|
|
34
45
|
const { instructions: _, ...rest } = config;
|
|
35
46
|
return rest;
|
package/src/tools/self-model.ts
CHANGED
|
@@ -84,7 +84,7 @@ async function composeSelfModel(days: number): Promise<string> {
|
|
|
84
84
|
const result = await inference({
|
|
85
85
|
system: buildPrompt(ai.name, principal.name),
|
|
86
86
|
user: inferenceUserContent(rawData, readFileOrEmpty(selfModelPath())),
|
|
87
|
-
|
|
87
|
+
tier: "medium",
|
|
88
88
|
maxTokens: 1500,
|
|
89
89
|
timeout: 90000,
|
|
90
90
|
caller: "self-model",
|