@oh-my-pi/pi-coding-agent 16.0.8 → 16.0.10
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 +33 -0
- package/dist/cli.js +3004 -2976
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/collab/host.d.ts +2 -2
- package/dist/types/collab/protocol.d.ts +4 -5
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/model-resolver.d.ts +11 -2
- package/dist/types/config/settings-schema.d.ts +12 -2
- package/dist/types/goals/runtime.d.ts +4 -1
- package/dist/types/modes/print-mode.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/helpers/collab-qrcode.d.ts +13 -0
- package/dist/types/tools/index.d.ts +9 -1
- package/dist/types/utils/image-loading.d.ts +12 -0
- package/dist/types/utils/qrcode.d.ts +48 -0
- package/package.json +12 -12
- package/src/cli/args.ts +10 -1
- package/src/cli/flag-tables.ts +1 -0
- package/src/collab/host.ts +4 -4
- package/src/collab/protocol.ts +48 -15
- package/src/commands/launch.ts +3 -0
- package/src/config/config-file.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-registry.ts +16 -4
- package/src/config/model-resolver.ts +193 -35
- package/src/config/settings-schema.ts +14 -2
- package/src/config/settings.ts +3 -3
- package/src/goals/runtime.ts +19 -7
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +10 -2
- package/src/modes/components/oauth-selector.ts +31 -2
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/print-mode.ts +5 -1
- package/src/prompts/advisor/advise-tool.md +3 -1
- package/src/prompts/advisor/system.md +55 -12
- package/src/prompts/tools/inspect-image.md +1 -1
- package/src/sdk.ts +26 -7
- package/src/session/agent-session.ts +103 -16
- package/src/slash-commands/builtin-registry.ts +29 -11
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/thinking.ts +25 -5
- package/src/tools/index.ts +10 -1
- package/src/tools/inspect-image.ts +72 -9
- package/src/utils/file-mentions.ts +5 -2
- package/src/utils/image-loading.ts +58 -0
- package/src/utils/qrcode.ts +535 -0
|
@@ -72,6 +72,21 @@ export interface ScopedModel {
|
|
|
72
72
|
explicitThinkingLevel: boolean;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
interface ThinkingSuffixOptions {
|
|
76
|
+
allowMaxAlias?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ModelStringParseOptions extends ThinkingSuffixOptions {
|
|
80
|
+
isLiteralModelId?: (provider: string, id: string) => boolean;
|
|
81
|
+
}
|
|
82
|
+
const MAX_THINKING_SUFFIX_OPTIONS: ThinkingSuffixOptions = { allowMaxAlias: true };
|
|
83
|
+
|
|
84
|
+
function parseThinkingSuffix(value: string, options?: ThinkingSuffixOptions): ThinkingLevel | undefined {
|
|
85
|
+
const level = parseThinkingLevel(value);
|
|
86
|
+
if (level !== undefined) return level;
|
|
87
|
+
return options?.allowMaxAlias === true && value === "max" ? ThinkingLevel.XHigh : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
/**
|
|
76
91
|
* Split a trailing `:<level>` thinking selector off a model pattern.
|
|
77
92
|
*
|
|
@@ -81,27 +96,89 @@ export interface ScopedModel {
|
|
|
81
96
|
* role-alias callers pass `PREFIX_MODEL_ROLE.length` so the base is at least
|
|
82
97
|
* as long as the `pi/` prefix.
|
|
83
98
|
*/
|
|
84
|
-
function splitThinkingSuffix(
|
|
99
|
+
function splitThinkingSuffix(
|
|
100
|
+
pattern: string,
|
|
101
|
+
minColonIndex = -1,
|
|
102
|
+
options?: ThinkingSuffixOptions,
|
|
103
|
+
): { base: string; level?: ThinkingLevel } {
|
|
85
104
|
const colonIdx = pattern.lastIndexOf(":");
|
|
86
105
|
if (colonIdx <= minColonIndex) return { base: pattern };
|
|
87
|
-
const level =
|
|
106
|
+
const level = parseThinkingSuffix(pattern.slice(colonIdx + 1), options);
|
|
88
107
|
return level ? { base: pattern.slice(0, colonIdx), level } : { base: pattern };
|
|
89
108
|
}
|
|
90
109
|
|
|
110
|
+
function hasExactModelPattern(pattern: string, availableModels: readonly Model<Api>[]): boolean {
|
|
111
|
+
const normalized = pattern.toLowerCase();
|
|
112
|
+
return availableModels.some(
|
|
113
|
+
model => model.id.toLowerCase() === normalized || `${model.provider}/${model.id}`.toLowerCase() === normalized,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function matchingGlobModels(pattern: string, availableModels: readonly Model<Api>[]): Model<Api>[] {
|
|
118
|
+
const glob = new Bun.Glob(pattern.toLowerCase());
|
|
119
|
+
return availableModels.filter(model => {
|
|
120
|
+
const fullId = `${model.provider}/${model.id}`;
|
|
121
|
+
return glob.match(fullId.toLowerCase()) || glob.match(model.id.toLowerCase());
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveGlobScopePattern(
|
|
126
|
+
pattern: string,
|
|
127
|
+
availableModels: readonly Model<Api>[],
|
|
128
|
+
): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
|
|
129
|
+
const strictSuffix = splitThinkingSuffix(pattern);
|
|
130
|
+
if (strictSuffix.level !== undefined) {
|
|
131
|
+
return {
|
|
132
|
+
models: matchingGlobModels(strictSuffix.base, availableModels),
|
|
133
|
+
thinkingLevel: strictSuffix.level,
|
|
134
|
+
explicitThinkingLevel: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const maxSuffix = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
|
|
139
|
+
if (maxSuffix.level !== undefined) {
|
|
140
|
+
const literalMatches = matchingGlobModels(pattern, availableModels);
|
|
141
|
+
if (literalMatches.length > 0) {
|
|
142
|
+
return { models: literalMatches, thinkingLevel: undefined, explicitThinkingLevel: false };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
models: matchingGlobModels(maxSuffix.base, availableModels),
|
|
146
|
+
thinkingLevel: maxSuffix.level,
|
|
147
|
+
explicitThinkingLevel: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
models: matchingGlobModels(pattern, availableModels),
|
|
153
|
+
thinkingLevel: undefined,
|
|
154
|
+
explicitThinkingLevel: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
91
158
|
/**
|
|
92
159
|
* Parse a model string in "provider/modelId" format.
|
|
93
160
|
* Returns undefined if the format is invalid.
|
|
94
161
|
*/
|
|
95
162
|
export function parseModelString(
|
|
96
163
|
modelStr: string,
|
|
164
|
+
options?: ModelStringParseOptions,
|
|
97
165
|
): { provider: string; id: string; thinkingLevel?: ThinkingLevel } | undefined {
|
|
98
166
|
const slashIdx = modelStr.indexOf("/");
|
|
99
167
|
if (slashIdx <= 0) return undefined;
|
|
100
168
|
const id = modelStr.slice(slashIdx + 1);
|
|
101
169
|
const provider = modelStr.slice(0, slashIdx);
|
|
102
|
-
// Strip
|
|
103
|
-
const
|
|
104
|
-
|
|
170
|
+
// Strip strict thinking level suffixes first (e.g. "claude-sonnet-4-6:high" -> id "claude-sonnet-4-6", thinkingLevel "high").
|
|
171
|
+
const strict = splitThinkingSuffix(id);
|
|
172
|
+
if (strict.level) return { provider, id: strict.base, thinkingLevel: strict.level };
|
|
173
|
+
// `max` is a provider-facing alias for xhigh, but real model IDs can end in
|
|
174
|
+
// `:max`. Context-aware callers pass a literal lookup so those models win.
|
|
175
|
+
const maxAlias = splitThinkingSuffix(id, -1, options);
|
|
176
|
+
if (maxAlias.level) {
|
|
177
|
+
return options?.isLiteralModelId?.(provider, id) === true
|
|
178
|
+
? { provider, id }
|
|
179
|
+
: { provider, id: maxAlias.base, thinkingLevel: maxAlias.level };
|
|
180
|
+
}
|
|
181
|
+
return { provider, id };
|
|
105
182
|
}
|
|
106
183
|
|
|
107
184
|
/**
|
|
@@ -149,7 +226,10 @@ function getOpenRouterRouteSuffix(modelId: string): { baseId: string; suffix: st
|
|
|
149
226
|
}
|
|
150
227
|
|
|
151
228
|
const suffix = modelId.slice(colonIdx + 1).trim();
|
|
152
|
-
|
|
229
|
+
// `max` is a thinking-level alias (xhigh), never an OpenRouter route suffix, so
|
|
230
|
+
// `openrouter/<id>:max` falls through to the max-aware selector split instead of
|
|
231
|
+
// being cloned into a literal `<id>:max` model id with the reasoning level lost.
|
|
232
|
+
if (!suffix || parseThinkingSuffix(suffix, MAX_THINKING_SUFFIX_OPTIONS)) {
|
|
153
233
|
return undefined;
|
|
154
234
|
}
|
|
155
235
|
|
|
@@ -196,6 +276,50 @@ function cloneModelWithRequestedId(model: Model<Api>, requestedId: string): Mode
|
|
|
196
276
|
};
|
|
197
277
|
}
|
|
198
278
|
|
|
279
|
+
const AMAZON_BEDROCK_PROVIDER = "amazon-bedrock";
|
|
280
|
+
const BEDROCK_INFERENCE_PROFILE_ARN =
|
|
281
|
+
/^arn:aws(?:-[a-z]+)*:bedrock:[a-z0-9-]+:[0-9]*:(?:application-inference-profile|inference-profile)\/[a-z0-9][a-z0-9._:-]*$/i;
|
|
282
|
+
|
|
283
|
+
function hasBedrockInferenceProfileThinkingSuffix(modelId: string): boolean {
|
|
284
|
+
const { base, level } = splitThinkingSuffix(modelId);
|
|
285
|
+
return level !== undefined && BEDROCK_INFERENCE_PROFILE_ARN.test(base.trim());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function resolveBedrockInferenceProfileModelId(
|
|
289
|
+
modelId: string,
|
|
290
|
+
availableModels: readonly Model<Api>[],
|
|
291
|
+
): Model<Api> | undefined {
|
|
292
|
+
const requestedId = modelId.trim();
|
|
293
|
+
if (hasBedrockInferenceProfileThinkingSuffix(requestedId) || !BEDROCK_INFERENCE_PROFILE_ARN.test(requestedId)) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const template = availableModels.find(model => model.provider.toLowerCase() === AMAZON_BEDROCK_PROVIDER);
|
|
298
|
+
if (!template) return undefined;
|
|
299
|
+
|
|
300
|
+
return buildModel({
|
|
301
|
+
id: requestedId,
|
|
302
|
+
name: "Bedrock inference profile",
|
|
303
|
+
api: "bedrock-converse-stream",
|
|
304
|
+
provider: AMAZON_BEDROCK_PROVIDER,
|
|
305
|
+
baseUrl: template.baseUrl,
|
|
306
|
+
reasoning: false,
|
|
307
|
+
input: ["text"],
|
|
308
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
309
|
+
contextWindow: null,
|
|
310
|
+
maxTokens: null,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function resolveBedrockInferenceProfileReference(
|
|
315
|
+
provider: string,
|
|
316
|
+
modelId: string,
|
|
317
|
+
availableModels: readonly Model<Api>[],
|
|
318
|
+
): Model<Api> | undefined {
|
|
319
|
+
if (provider.toLowerCase() !== AMAZON_BEDROCK_PROVIDER) return undefined;
|
|
320
|
+
return resolveBedrockInferenceProfileModelId(modelId, availableModels);
|
|
321
|
+
}
|
|
322
|
+
|
|
199
323
|
const UPSTREAM_ROUTING_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
|
|
200
324
|
|
|
201
325
|
/**
|
|
@@ -289,6 +413,11 @@ export function resolveProviderModelReference(
|
|
|
289
413
|
}
|
|
290
414
|
}
|
|
291
415
|
|
|
416
|
+
const bedrockInferenceProfile = resolveBedrockInferenceProfileReference(provider, modelId, availableModels);
|
|
417
|
+
if (bedrockInferenceProfile) {
|
|
418
|
+
return bedrockInferenceProfile;
|
|
419
|
+
}
|
|
420
|
+
|
|
292
421
|
if (normalizedProvider !== "openrouter") {
|
|
293
422
|
return undefined;
|
|
294
423
|
}
|
|
@@ -470,6 +599,7 @@ function findExactCanonicalModelMatch(
|
|
|
470
599
|
* The single model-matching engine. Tries, in order:
|
|
471
600
|
* 1. exact `provider/id` reference (variant-alias and OpenRouter routed/date
|
|
472
601
|
* fallbacks included),
|
|
602
|
+
|
|
473
603
|
* 2. exact canonical id (coalesces provider variants),
|
|
474
604
|
* 3. exact bare id (preference-ranked),
|
|
475
605
|
* 4. retired effort-tier variant alias (collapsed catalog entries),
|
|
@@ -502,6 +632,11 @@ function matchModel(
|
|
|
502
632
|
return pickPreferredModel(exactMatches, context);
|
|
503
633
|
}
|
|
504
634
|
|
|
635
|
+
const bedrockInferenceProfile = resolveBedrockInferenceProfileModelId(modelPattern, availableModels);
|
|
636
|
+
if (bedrockInferenceProfile) {
|
|
637
|
+
return bedrockInferenceProfile;
|
|
638
|
+
}
|
|
639
|
+
|
|
505
640
|
// Retired effort-tier variant ids (bare, no provider prefix) resolve to
|
|
506
641
|
// their collapsed logical model; models from the providers whose table
|
|
507
642
|
// declared the alias win ties. Auto-derived `X-thinking` pairs resolve
|
|
@@ -625,8 +760,10 @@ function parseModelPatternWithContext(
|
|
|
625
760
|
return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
626
761
|
}
|
|
627
762
|
|
|
628
|
-
// No match - try stripping a valid thinking suffix and recursing
|
|
629
|
-
|
|
763
|
+
// No match - try stripping a valid thinking suffix and recursing.
|
|
764
|
+
// `max` is accepted only after the full pattern failed, so literal model IDs
|
|
765
|
+
// ending in `:max` keep winning over the alias.
|
|
766
|
+
const { base, level } = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
|
|
630
767
|
if (level) {
|
|
631
768
|
const result = parseModelPatternWithContext(base, availableModels, context, options);
|
|
632
769
|
if (result.model) {
|
|
@@ -730,7 +867,11 @@ function resolveDefaultInheritedPatterns(
|
|
|
730
867
|
|
|
731
868
|
const resolved: string[] = [];
|
|
732
869
|
for (const pattern of normalizeModelPatternList(configuredDefault)) {
|
|
733
|
-
const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
|
|
870
|
+
const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
|
|
871
|
+
pattern,
|
|
872
|
+
PREFIX_MODEL_ROLE.length,
|
|
873
|
+
MAX_THINKING_SUFFIX_OPTIONS,
|
|
874
|
+
);
|
|
734
875
|
const aliasRole = getModelRoleAlias(aliasCandidate);
|
|
735
876
|
if (aliasRole === role) {
|
|
736
877
|
// Self-alias (e.g. modelRoles.default = "pi/smol") would loop back to the
|
|
@@ -765,7 +906,11 @@ function resolveConfiguredRolePattern(
|
|
|
765
906
|
const normalized = value.trim();
|
|
766
907
|
if (!normalized) return undefined;
|
|
767
908
|
|
|
768
|
-
const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
|
|
909
|
+
const { base: aliasCandidate, level: thinkingLevel } = splitThinkingSuffix(
|
|
910
|
+
normalized,
|
|
911
|
+
PREFIX_MODEL_ROLE.length,
|
|
912
|
+
MAX_THINKING_SUFFIX_OPTIONS,
|
|
913
|
+
);
|
|
769
914
|
const role = getModelRoleAlias(aliasCandidate);
|
|
770
915
|
if (!role) return [normalized];
|
|
771
916
|
if (visited.has(role)) return undefined;
|
|
@@ -888,9 +1033,19 @@ export function resolveModelRoleValue(
|
|
|
888
1033
|
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning };
|
|
889
1034
|
}
|
|
890
1035
|
|
|
1036
|
+
interface ExplicitThinkingSelectorOptions {
|
|
1037
|
+
isLiteralModelId?: (provider: string, id: string) => boolean;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function isLiteralModelSelector(value: string, options?: ExplicitThinkingSelectorOptions): boolean {
|
|
1041
|
+
const parsed = parseModelString(value);
|
|
1042
|
+
return parsed !== undefined && options?.isLiteralModelId?.(parsed.provider, parsed.id) === true;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
891
1045
|
export function extractExplicitThinkingSelector(
|
|
892
1046
|
value: string | undefined,
|
|
893
1047
|
settings?: Settings,
|
|
1048
|
+
options?: ExplicitThinkingSelectorOptions,
|
|
894
1049
|
): ThinkingLevel | undefined {
|
|
895
1050
|
if (!value) return undefined;
|
|
896
1051
|
const normalized = value.trim();
|
|
@@ -900,9 +1055,13 @@ export function extractExplicitThinkingSelector(
|
|
|
900
1055
|
let current = normalized;
|
|
901
1056
|
while (!visited.has(current)) {
|
|
902
1057
|
visited.add(current);
|
|
903
|
-
const
|
|
904
|
-
if (
|
|
905
|
-
return
|
|
1058
|
+
const strictSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length).level;
|
|
1059
|
+
if (strictSelector) {
|
|
1060
|
+
return strictSelector;
|
|
1061
|
+
}
|
|
1062
|
+
const maxSelector = splitThinkingSuffix(current, PREFIX_MODEL_ROLE.length, MAX_THINKING_SUFFIX_OPTIONS).level;
|
|
1063
|
+
if (maxSelector && (current.startsWith(PREFIX_MODEL_ROLE) || !isLiteralModelSelector(current, options))) {
|
|
1064
|
+
return maxSelector;
|
|
906
1065
|
}
|
|
907
1066
|
const expanded = expandRoleAlias(current, settings).trim();
|
|
908
1067
|
if (!expanded || expanded === current) break;
|
|
@@ -922,10 +1081,15 @@ export function resolveModelFromString(
|
|
|
922
1081
|
matchPreferences?: ModelMatchPreferences,
|
|
923
1082
|
modelRegistry?: CanonicalModelRegistry,
|
|
924
1083
|
): Model<Api> | undefined {
|
|
925
|
-
const
|
|
1084
|
+
const exact = available.find(model => `${model.provider}/${model.id}` === value);
|
|
1085
|
+
if (exact) return exact;
|
|
1086
|
+
const parsed = parseModelString(value, {
|
|
1087
|
+
...MAX_THINKING_SUFFIX_OPTIONS,
|
|
1088
|
+
isLiteralModelId: (provider, id) => available.some(model => model.provider === provider && model.id === id),
|
|
1089
|
+
});
|
|
926
1090
|
if (parsed) {
|
|
927
|
-
const
|
|
928
|
-
if (
|
|
1091
|
+
const parsedExact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
1092
|
+
if (parsedExact) return parsedExact;
|
|
929
1093
|
}
|
|
930
1094
|
return parseModelPattern(value, available, matchPreferences, { modelRegistry }).model;
|
|
931
1095
|
}
|
|
@@ -1065,7 +1229,10 @@ function resolveExactCanonicalScopePattern(
|
|
|
1065
1229
|
modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
|
|
1066
1230
|
availableModels: Model<Api>[],
|
|
1067
1231
|
): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } | undefined {
|
|
1068
|
-
|
|
1232
|
+
if (pattern.endsWith(":max") && hasExactModelPattern(pattern, availableModels)) {
|
|
1233
|
+
return undefined;
|
|
1234
|
+
}
|
|
1235
|
+
const { base: canonicalId, level: thinkingLevel } = splitThinkingSuffix(pattern, -1, MAX_THINKING_SUFFIX_OPTIONS);
|
|
1069
1236
|
const explicitThinkingLevel = thinkingLevel !== undefined;
|
|
1070
1237
|
|
|
1071
1238
|
const variants = modelRegistry
|
|
@@ -1111,17 +1278,13 @@ export async function resolveModelScope(
|
|
|
1111
1278
|
for (const pattern of patterns) {
|
|
1112
1279
|
// Check if pattern contains glob characters
|
|
1113
1280
|
if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
|
|
1114
|
-
// Extract optional thinking level suffix (e.g., "provider/*:high")
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const fullId = `${m.provider}/${m.id}`;
|
|
1122
|
-
const glob = new Bun.Glob(globPattern.toLowerCase());
|
|
1123
|
-
return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
|
|
1124
|
-
});
|
|
1281
|
+
// Extract optional thinking level suffix (e.g., "provider/*:high") only
|
|
1282
|
+
// after literal `:max` globs had a chance to match real model IDs.
|
|
1283
|
+
const {
|
|
1284
|
+
models: matchingModels,
|
|
1285
|
+
thinkingLevel,
|
|
1286
|
+
explicitThinkingLevel,
|
|
1287
|
+
} = resolveGlobScopePattern(pattern, availableModels);
|
|
1125
1288
|
|
|
1126
1289
|
if (matchingModels.length === 0) {
|
|
1127
1290
|
logger.warn(`No models match pattern "${pattern}"`);
|
|
@@ -1226,13 +1389,8 @@ export function filterAvailableModelsByEnabledPatterns(
|
|
|
1226
1389
|
|
|
1227
1390
|
for (const pattern of patterns) {
|
|
1228
1391
|
if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
for (const model of available) {
|
|
1232
|
-
const fullId = `${model.provider}/${model.id}`.toLowerCase();
|
|
1233
|
-
if (glob.match(fullId) || glob.match(model.id.toLowerCase())) {
|
|
1234
|
-
addAllowed(model);
|
|
1235
|
-
}
|
|
1392
|
+
for (const model of resolveGlobScopePattern(pattern, available).models) {
|
|
1393
|
+
addAllowed(model);
|
|
1236
1394
|
}
|
|
1237
1395
|
continue;
|
|
1238
1396
|
}
|
|
@@ -872,7 +872,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
872
872
|
// Reasoning and prompts
|
|
873
873
|
defaultThinkingLevel: {
|
|
874
874
|
type: "enum",
|
|
875
|
-
values: [...THINKING_EFFORTS, AUTO_THINKING],
|
|
875
|
+
values: [...THINKING_EFFORTS, AUTO_THINKING, "max"],
|
|
876
876
|
default: "high",
|
|
877
877
|
ui: {
|
|
878
878
|
tab: "model",
|
|
@@ -1519,6 +1519,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
1519
1519
|
},
|
|
1520
1520
|
},
|
|
1521
1521
|
|
|
1522
|
+
"collab.webUrl": {
|
|
1523
|
+
type: "string",
|
|
1524
|
+
default: "",
|
|
1525
|
+
ui: {
|
|
1526
|
+
tab: "interaction",
|
|
1527
|
+
group: "Collab",
|
|
1528
|
+
label: "Web UI URL",
|
|
1529
|
+
description:
|
|
1530
|
+
"Browser UI used by /collab links; empty derives from collab.relayUrl; explicit http:// is localhost-only",
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1522
1534
|
"collab.displayName": {
|
|
1523
1535
|
type: "string",
|
|
1524
1536
|
default: "",
|
|
@@ -1878,7 +1890,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1878
1890
|
{ value: "anthropic", label: "Anthropic", description: "Use Anthropic-style in-band tool calls." },
|
|
1879
1891
|
{ value: "deepseek", label: "DeepSeek", description: "Use DeepSeek-style in-band tool calls." },
|
|
1880
1892
|
{ value: "harmony", label: "Harmony", description: "Use Harmony-style in-band tool calls." },
|
|
1881
|
-
{ value: "pi", label: "Pi", description: "Use the Pi owned dialect." },
|
|
1893
|
+
{ value: "pi", label: "Pi", description: "Use the Pi owned dialect (compact sigil-delimited tool calls)." },
|
|
1882
1894
|
{ value: "qwen3", label: "Qwen3", description: "Use the Qwen3 owned dialect." },
|
|
1883
1895
|
{ value: "gemini", label: "Gemini", description: "Use the Gemini owned dialect." },
|
|
1884
1896
|
{ value: "gemma", label: "Gemma", description: "Use the Gemma owned dialect." },
|
package/src/config/settings.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
procmgr,
|
|
25
25
|
setDefaultTabWidth,
|
|
26
26
|
} from "@oh-my-pi/pi-utils";
|
|
27
|
-
import { YAML } from "bun";
|
|
27
|
+
import { JSONC, YAML } from "bun";
|
|
28
28
|
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
|
29
29
|
import type { ModelRole } from "../config/model-roles";
|
|
30
30
|
import { loadCapability } from "../discovery";
|
|
@@ -668,9 +668,9 @@ export class Settings {
|
|
|
668
668
|
// 1. Migrate from settings.json
|
|
669
669
|
const settingsJsonPath = path.join(this.#agentDir, "settings.json");
|
|
670
670
|
try {
|
|
671
|
-
const parsed =
|
|
671
|
+
const parsed: unknown = JSONC.parse(await Bun.file(settingsJsonPath).text());
|
|
672
672
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
673
|
-
settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed));
|
|
673
|
+
settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed as RawSettings));
|
|
674
674
|
migrated = true;
|
|
675
675
|
try {
|
|
676
676
|
fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);
|
package/src/goals/runtime.ts
CHANGED
|
@@ -178,8 +178,8 @@ export class GoalRuntime {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
#markActiveAccounting(goal: Goal): void {
|
|
182
|
-
if (this.#wallClock.activeGoalId !== goal.id) {
|
|
181
|
+
#markActiveAccounting(goal: Goal, resetWallClock = false): void {
|
|
182
|
+
if (resetWallClock || this.#wallClock.activeGoalId !== goal.id) {
|
|
183
183
|
this.#wallClock = { lastAccountedAt: this.#now(), activeGoalId: goal.id };
|
|
184
184
|
}
|
|
185
185
|
if (this.#turnSnapshot) {
|
|
@@ -195,6 +195,12 @@ export class GoalRuntime {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
clearAccounting(): void {
|
|
199
|
+
this.#turnSnapshot = undefined;
|
|
200
|
+
this.#clearActiveAccounting();
|
|
201
|
+
this.#budgetReportedFor = undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
198
204
|
onTurnStart(turnId: string, baselineUsage: GoalTokenUsage): void {
|
|
199
205
|
this.#turnSnapshot = { turnId, baselineUsage: { ...baselineUsage } };
|
|
200
206
|
const state = this.#host.getState();
|
|
@@ -235,7 +241,7 @@ export class GoalRuntime {
|
|
|
235
241
|
return;
|
|
236
242
|
}
|
|
237
243
|
await this.#withAccounting(async () => {
|
|
238
|
-
await this.#flushUsageLocked("suppressed");
|
|
244
|
+
await this.#flushUsageLocked("suppressed", undefined, options?.reason === "internal");
|
|
239
245
|
this.#turnSnapshot = undefined;
|
|
240
246
|
if (options?.reason !== "interrupted") return;
|
|
241
247
|
const cloned = this.#getStateClone();
|
|
@@ -249,9 +255,14 @@ export class GoalRuntime {
|
|
|
249
255
|
});
|
|
250
256
|
}
|
|
251
257
|
|
|
252
|
-
async onThreadResumed(): Promise<GoalModeState | undefined> {
|
|
258
|
+
async onThreadResumed(options?: { preserveActiveGoal?: boolean }): Promise<GoalModeState | undefined> {
|
|
253
259
|
const state = this.#getStateClone();
|
|
254
260
|
if (!state) return undefined;
|
|
261
|
+
if (options?.preserveActiveGoal && state.enabled && state.goal.status === "active") {
|
|
262
|
+
this.#markActiveAccounting(state.goal, true);
|
|
263
|
+
await this.#commitState(state, { emit: true });
|
|
264
|
+
return state;
|
|
265
|
+
}
|
|
255
266
|
if (state.goal.status === "active") {
|
|
256
267
|
state.enabled = false;
|
|
257
268
|
state.goal.status = "paused";
|
|
@@ -301,6 +312,7 @@ export class GoalRuntime {
|
|
|
301
312
|
async #flushUsageLocked(
|
|
302
313
|
steering: GoalBudgetSteering,
|
|
303
314
|
currentUsage: GoalTokenUsage = this.#host.getCurrentUsage(),
|
|
315
|
+
persistWallClock = false,
|
|
304
316
|
): Promise<void> {
|
|
305
317
|
const state = this.#getStateClone();
|
|
306
318
|
if (!state?.enabled || !isAccountingStatus(state.goal)) return;
|
|
@@ -333,10 +345,10 @@ export class GoalRuntime {
|
|
|
333
345
|
if (this.#wallClock.activeGoalId === state.goal.id && wallSeconds > 0) {
|
|
334
346
|
this.#wallClock.lastAccountedAt += wallSeconds * 1000;
|
|
335
347
|
}
|
|
336
|
-
|
|
337
348
|
// Persisting wall-clock-only accounting on every tool event bloats /goal sessions with full
|
|
338
|
-
// objective snapshots. Keep
|
|
339
|
-
|
|
349
|
+
// objective snapshots. Keep normal tool flushes in memory/UI only, but make wall-clock
|
|
350
|
+
// usage durable before internal session switches because the active runtime is leaving.
|
|
351
|
+
const shouldPersistUsage = tokenDelta > 0 || flippedToBudgetLimited || (persistWallClock && wallSeconds > 0);
|
|
340
352
|
await this.#commitState(state, { persist: shouldPersistUsage ? "goal" : undefined });
|
|
341
353
|
|
|
342
354
|
if (state.goal.status !== "budget-limited") {
|