@oh-my-pi/pi-coding-agent 16.0.9 → 16.0.11
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 +58 -0
- package/dist/cli.js +3402 -3443
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +52 -0
- package/dist/types/collab/host.d.ts +2 -2
- package/dist/types/collab/protocol.d.ts +4 -5
- package/dist/types/commit/agentic/agent.d.ts +1 -1
- package/dist/types/config/model-resolver.d.ts +11 -2
- package/dist/types/config/settings-schema.d.ts +12 -6
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/modes/components/agent-hub.d.ts +6 -1
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +39 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +42 -0
- package/dist/types/modes/controllers/command-controller.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/registry/agent-registry.d.ts +10 -3
- package/dist/types/session/agent-session.d.ts +13 -0
- package/dist/types/session/compact-modes.d.ts +60 -0
- package/dist/types/session/streaming-output.d.ts +0 -2
- 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/__tests__/json-tree.test.d.ts +1 -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/advisor/index.ts +1 -0
- package/src/advisor/transcript-recorder.ts +136 -0
- package/src/cli/args.ts +7 -1
- package/src/cli/stats-cli.ts +2 -11
- package/src/collab/host.ts +29 -17
- package/src/collab/protocol.ts +48 -15
- package/src/commit/agentic/agent.ts +2 -1
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/changelog/index.ts +1 -1
- package/src/commit/map-reduce/map-phase.ts +1 -1
- package/src/commit/map-reduce/utils.ts +1 -1
- 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 -7
- package/src/config/settings.ts +3 -9
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/renderer.ts +7 -7
- package/src/eval/js/tool-bridge.ts +3 -2
- package/src/eval/py/prelude.py +3 -2
- package/src/export/html/tool-views.generated.js +28 -28
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/history-protocol.ts +8 -3
- package/src/irc/bus.ts +8 -0
- package/src/lsp/index.ts +2 -2
- package/src/main.ts +6 -3
- package/src/modes/acp/acp-agent.ts +63 -0
- package/src/modes/components/agent-hub.ts +97 -920
- package/src/modes/components/agent-transcript-viewer.ts +461 -0
- package/src/modes/components/chat-transcript-builder.ts +462 -0
- package/src/modes/components/diff.ts +12 -35
- package/src/modes/components/oauth-selector.ts +31 -2
- package/src/modes/controllers/command-controller.ts +12 -2
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +8 -1
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/types.ts +2 -1
- package/src/prompts/tools/inspect-image.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/registry/agent-registry.ts +13 -4
- package/src/sdk.ts +27 -8
- package/src/session/agent-session.ts +185 -17
- package/src/session/compact-modes.ts +105 -0
- package/src/session/session-dump-format.ts +1 -1
- package/src/session/session-history-format.ts +1 -1
- package/src/session/streaming-output.ts +5 -5
- package/src/slash-commands/builtin-registry.ts +45 -15
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/task/executor.ts +1 -1
- package/src/task/output-manager.ts +5 -0
- package/src/thinking.ts +25 -5
- package/src/tools/__tests__/json-tree.test.ts +35 -0
- package/src/tools/approval.ts +1 -1
- package/src/tools/bash.ts +0 -1
- package/src/tools/browser.ts +0 -1
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/index.ts +10 -1
- package/src/tools/inspect-image.ts +72 -9
- package/src/tools/irc.ts +1 -1
- package/src/tools/json-tree.ts +22 -5
- package/src/tools/read.ts +5 -6
- package/src/utils/file-mentions.ts +5 -2
- package/src/utils/image-loading.ts +58 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/web/scrapers/firefox-addons.ts +1 -1
- package/src/web/scrapers/github.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/metacpan.ts +2 -2
- package/src/web/scrapers/nvd.ts +2 -2
- package/src/web/scrapers/ollama.ts +1 -1
- package/src/web/scrapers/opencorporates.ts +1 -1
- package/src/web/scrapers/pub-dev.ts +1 -1
- package/src/web/scrapers/repology.ts +1 -1
- package/src/web/scrapers/sourcegraph.ts +1 -1
- package/src/web/scrapers/terraform.ts +6 -6
- package/src/web/scrapers/wikidata.ts +2 -2
- package/src/workspace-tree.ts +1 -1
|
@@ -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
|
}
|
|
@@ -809,11 +809,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
809
809
|
description: "Remove the 1-character horizontal padding from the left and right of the terminal output",
|
|
810
810
|
},
|
|
811
811
|
},
|
|
812
|
-
// Display rendering
|
|
813
|
-
"display.tabWidth": {
|
|
814
|
-
type: "number",
|
|
815
|
-
default: 3,
|
|
816
|
-
},
|
|
817
812
|
|
|
818
813
|
"display.shimmer": {
|
|
819
814
|
type: "enum",
|
|
@@ -872,7 +867,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
872
867
|
// Reasoning and prompts
|
|
873
868
|
defaultThinkingLevel: {
|
|
874
869
|
type: "enum",
|
|
875
|
-
values: [...THINKING_EFFORTS, AUTO_THINKING],
|
|
870
|
+
values: [...THINKING_EFFORTS, AUTO_THINKING, "max"],
|
|
876
871
|
default: "high",
|
|
877
872
|
ui: {
|
|
878
873
|
tab: "model",
|
|
@@ -1519,6 +1514,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
1519
1514
|
},
|
|
1520
1515
|
},
|
|
1521
1516
|
|
|
1517
|
+
"collab.webUrl": {
|
|
1518
|
+
type: "string",
|
|
1519
|
+
default: "",
|
|
1520
|
+
ui: {
|
|
1521
|
+
tab: "interaction",
|
|
1522
|
+
group: "Collab",
|
|
1523
|
+
label: "Web UI URL",
|
|
1524
|
+
description:
|
|
1525
|
+
"Browser UI used by /collab links; empty derives from collab.relayUrl; explicit http:// is localhost-only",
|
|
1526
|
+
},
|
|
1527
|
+
},
|
|
1528
|
+
|
|
1522
1529
|
"collab.displayName": {
|
|
1523
1530
|
type: "string",
|
|
1524
1531
|
default: "",
|
|
@@ -1878,7 +1885,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1878
1885
|
{ value: "anthropic", label: "Anthropic", description: "Use Anthropic-style in-band tool calls." },
|
|
1879
1886
|
{ value: "deepseek", label: "DeepSeek", description: "Use DeepSeek-style in-band tool calls." },
|
|
1880
1887
|
{ value: "harmony", label: "Harmony", description: "Use Harmony-style in-band tool calls." },
|
|
1881
|
-
{ value: "pi", label: "Pi", description: "Use the Pi owned dialect." },
|
|
1888
|
+
{ value: "pi", label: "Pi", description: "Use the Pi owned dialect (compact sigil-delimited tool calls)." },
|
|
1882
1889
|
{ value: "qwen3", label: "Qwen3", description: "Use the Qwen3 owned dialect." },
|
|
1883
1890
|
{ value: "gemini", label: "Gemini", description: "Use the Gemini owned dialect." },
|
|
1884
1891
|
{ value: "gemma", label: "Gemma", description: "Use the Gemma owned dialect." },
|
package/src/config/settings.ts
CHANGED
|
@@ -22,9 +22,8 @@ import {
|
|
|
22
22
|
isEnoent,
|
|
23
23
|
logger,
|
|
24
24
|
procmgr,
|
|
25
|
-
setDefaultTabWidth,
|
|
26
25
|
} from "@oh-my-pi/pi-utils";
|
|
27
|
-
import { YAML } from "bun";
|
|
26
|
+
import { JSONC, YAML } from "bun";
|
|
28
27
|
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
|
29
28
|
import type { ModelRole } from "../config/model-roles";
|
|
30
29
|
import { loadCapability } from "../discovery";
|
|
@@ -668,9 +667,9 @@ export class Settings {
|
|
|
668
667
|
// 1. Migrate from settings.json
|
|
669
668
|
const settingsJsonPath = path.join(this.#agentDir, "settings.json");
|
|
670
669
|
try {
|
|
671
|
-
const parsed =
|
|
670
|
+
const parsed: unknown = JSONC.parse(await Bun.file(settingsJsonPath).text());
|
|
672
671
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
673
|
-
settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed));
|
|
672
|
+
settings = this.#deepMerge(settings, this.#migrateRawSettings(parsed as RawSettings));
|
|
674
673
|
migrated = true;
|
|
675
674
|
try {
|
|
676
675
|
fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);
|
|
@@ -1103,11 +1102,6 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
1103
1102
|
});
|
|
1104
1103
|
}
|
|
1105
1104
|
},
|
|
1106
|
-
"display.tabWidth": value => {
|
|
1107
|
-
if (typeof value === "number") {
|
|
1108
|
-
setDefaultTabWidth(value);
|
|
1109
|
-
}
|
|
1110
|
-
},
|
|
1111
1105
|
"provider.appendOnlyContext": value => {
|
|
1112
1106
|
if (typeof value === "string") {
|
|
1113
1107
|
appendOnlyModeSignal.fire(value);
|
|
@@ -102,7 +102,7 @@ const HASHLINE_LINE_PREFIX = /^[ *]?(\d+)(?:-(\d+))?:/;
|
|
|
102
102
|
/**
|
|
103
103
|
* The 1-indexed file lines a hashline-formatted body actually displayed.
|
|
104
104
|
* Single `NN:` rows contribute that line; a collapsed summary `NN-MM:` row
|
|
105
|
-
* (a `{
|
|
105
|
+
* (a `{ … }` brace pair) contributes only its boundary lines `NN` and `MM` —
|
|
106
106
|
* the elided interior was never shown, so editing inside it must be rejected.
|
|
107
107
|
*/
|
|
108
108
|
export function parseSeenLinesFromHashlineBody(body: string): number[] {
|
package/src/edit/renderer.ts
CHANGED
|
@@ -230,7 +230,7 @@ function truncateEditTitlePath(displayPath: string, maxWidth: number | undefined
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function formatEditTitlePath(pathValue: string, maxWidth?: number): string {
|
|
233
|
-
return truncateEditTitlePath(replaceTabs(shortenPath(pathValue)
|
|
233
|
+
return truncateEditTitlePath(replaceTabs(shortenPath(pathValue)), maxWidth);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
function formatEditPathDisplay(
|
|
@@ -326,11 +326,11 @@ function renderEditHeader(
|
|
|
326
326
|
return buildHeader(fitted.description);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
function renderPlainTextPreview(text: string, uiTheme: Theme,
|
|
329
|
+
function renderPlainTextPreview(text: string, uiTheme: Theme, _filePath?: string): string {
|
|
330
330
|
const previewLines = sanitizeText(text).split("\n");
|
|
331
331
|
let preview = "\n\n";
|
|
332
332
|
for (const line of previewLines.slice(0, CALL_TEXT_PREVIEW_LINES)) {
|
|
333
|
-
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line
|
|
333
|
+
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), CALL_TEXT_PREVIEW_WIDTH))}\n`;
|
|
334
334
|
}
|
|
335
335
|
if (previewLines.length > CALL_TEXT_PREVIEW_LINES) {
|
|
336
336
|
preview += uiTheme.fg("dim", `… ${previewLines.length - CALL_TEXT_PREVIEW_LINES} more lines`);
|
|
@@ -395,7 +395,7 @@ function formatMultiFileStreamingDiff(
|
|
|
395
395
|
if (!preview.diff && !preview.error) continue;
|
|
396
396
|
const header = uiTheme.fg("dim", `\n\n── ${shortenPath(preview.path)} ──`);
|
|
397
397
|
if (preview.error) {
|
|
398
|
-
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(preview.error
|
|
398
|
+
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(preview.error))}`);
|
|
399
399
|
continue;
|
|
400
400
|
}
|
|
401
401
|
if (preview.diff) {
|
|
@@ -637,7 +637,7 @@ export const editToolRenderer = {
|
|
|
637
637
|
callPreviewCaches,
|
|
638
638
|
);
|
|
639
639
|
if (applyPatchSummary?.error) {
|
|
640
|
-
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error
|
|
640
|
+
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error), Math.max(1, width - 2)))}`;
|
|
641
641
|
}
|
|
642
642
|
const bodyLines = body ? body.split("\n") : [];
|
|
643
643
|
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
@@ -733,11 +733,11 @@ function renderSingleFileResult(
|
|
|
733
733
|
|
|
734
734
|
let body = "";
|
|
735
735
|
if (isError) {
|
|
736
|
-
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText
|
|
736
|
+
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText));
|
|
737
737
|
} else if (details?.diff) {
|
|
738
738
|
body = renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn, diffSectionCache);
|
|
739
739
|
} else if (editDiffPreview) {
|
|
740
|
-
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error
|
|
740
|
+
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error));
|
|
741
741
|
else if (editDiffPreview.diff)
|
|
742
742
|
body = renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, renderDiffFn, diffSectionCache);
|
|
743
743
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
2
3
|
import type { ToolSession } from "../../tools";
|
|
3
4
|
import { ToolError } from "../../tools/tool-errors";
|
|
4
5
|
import { EVAL_AGENT_BRIDGE_NAME, runEvalAgent } from "../agent-bridge";
|
|
@@ -48,8 +49,8 @@ function normalizeArgs(args: unknown): unknown {
|
|
|
48
49
|
return args;
|
|
49
50
|
}
|
|
50
51
|
const record = { ...(args as Record<string, unknown>) };
|
|
51
|
-
if (record
|
|
52
|
-
record
|
|
52
|
+
if (record[INTENT_FIELD] === undefined) {
|
|
53
|
+
record[INTENT_FIELD] = "js prelude";
|
|
53
54
|
}
|
|
54
55
|
return record;
|
|
55
56
|
}
|
package/src/eval/py/prelude.py
CHANGED
|
@@ -5,6 +5,7 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import os, json, math, re
|
|
7
7
|
from urllib.parse import unquote
|
|
8
|
+
INTENT_FIELD = "_i"
|
|
8
9
|
|
|
9
10
|
# __omp_display is injected by runner.py before the prelude executes; it
|
|
10
11
|
# mirrors IPython's display() semantics with the same MIME bundle output.
|
|
@@ -479,8 +480,8 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
479
480
|
f"tool.{self._name}(...) expects a dict of arguments (got {type(args).__name__})"
|
|
480
481
|
)
|
|
481
482
|
merged.update(kwargs)
|
|
482
|
-
if
|
|
483
|
-
merged[
|
|
483
|
+
if INTENT_FIELD not in merged:
|
|
484
|
+
merged[INTENT_FIELD] = "py prelude"
|
|
484
485
|
return _bridge_call(self._name, merged)
|
|
485
486
|
|
|
486
487
|
class _ToolProxy:
|