jeopi-cli 16.5.1 → 16.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/cli.js +2520 -2516
- package/dist/types/tools/builtin-names.d.ts +2 -1
- package/dist/types/web/search/providers/codex.d.ts +2 -2
- package/package.json +12 -12
- package/src/config/model-registry.ts +102 -20
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/controllers/streaming-reveal.ts +21 -2
- package/src/session/session-persistence.ts +22 -4
- package/src/tools/builtin-names.ts +11 -2
- package/src/tools/index.ts +2 -2
- package/src/web/search/providers/codex.ts +7 -12
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const BUILTIN_TOOL_NAMES: readonly ["read", "bash", "edit", "ast_grep", "ast_edit", "ask", "debug", "eval", "ssh", "github", "glob", "grep", "lsp", "inspect_image", "vision_verify", "browser", "checkpoint", "rewind", "task", "job", "irc", "subagent", "todo", "web_search", "search_tool_bm25", "write", "memory_edit", "retain", "recall", "reflect", "learn", "manage_skill"];
|
|
2
2
|
export type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number];
|
|
3
|
-
|
|
3
|
+
export declare const HIDDEN_TOOL_NAMES: readonly ["yield", "report_finding", "report_tool_issue", "resolve", "goal"];
|
|
4
|
+
/** Canonicalize built-in IDs and legacy aliases. Leave plugin names unchanged. */
|
|
4
5
|
export declare function normalizeToolName(name: string): string;
|
|
5
6
|
/** Normalize and deduplicate tool names while preserving first-seen order. */
|
|
6
7
|
export declare function normalizeToolNames(names: Iterable<string>): string[];
|
|
@@ -17,8 +17,8 @@ export interface CodexSearchParams {
|
|
|
17
17
|
* Default-model behavior:
|
|
18
18
|
* - If `PI_CODEX_WEB_SEARCH_MODEL` is set, use it exactly once and surface any
|
|
19
19
|
* upstream error verbatim.
|
|
20
|
-
* - Otherwise prefer
|
|
21
|
-
*
|
|
20
|
+
* - Otherwise prefer known bundled defaults, then include newly cataloged GPT
|
|
21
|
+
* models, and retry the next candidate only when Codex returns the
|
|
22
22
|
* known 400 "model is not supported" family. This avoids selecting
|
|
23
23
|
* `gpt-5-codex-mini` first on ChatGPT accounts, which OpenAI rejects.
|
|
24
24
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-cli",
|
|
4
|
-
"version": "16.5.
|
|
4
|
+
"version": "16.5.6",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
58
58
|
"@babel/parser": "^7.29.7",
|
|
59
59
|
"@mozilla/readability": "^0.6.0",
|
|
60
|
-
"jeopi-hashline": "16.5.
|
|
61
|
-
"jeopi-stats": "16.5.
|
|
62
|
-
"jeopi-agent-core": "16.5.
|
|
63
|
-
"jeopi-ai": "16.5.
|
|
64
|
-
"jeopi-catalog": "16.5.
|
|
65
|
-
"jeopi-mnemopi": "16.5.
|
|
66
|
-
"jeopi-natives": "16.5.
|
|
67
|
-
"jeopi-tui": "16.5.
|
|
68
|
-
"jeopi-utils": "16.5.
|
|
69
|
-
"jeopi-wire": "16.5.
|
|
70
|
-
"jeopi-snapcompact": "16.5.
|
|
60
|
+
"jeopi-hashline": "16.5.6",
|
|
61
|
+
"jeopi-stats": "16.5.6",
|
|
62
|
+
"jeopi-agent-core": "16.5.6",
|
|
63
|
+
"jeopi-ai": "16.5.6",
|
|
64
|
+
"jeopi-catalog": "16.5.6",
|
|
65
|
+
"jeopi-mnemopi": "16.5.6",
|
|
66
|
+
"jeopi-natives": "16.5.6",
|
|
67
|
+
"jeopi-tui": "16.5.6",
|
|
68
|
+
"jeopi-utils": "16.5.6",
|
|
69
|
+
"jeopi-wire": "16.5.6",
|
|
70
|
+
"jeopi-snapcompact": "16.5.6",
|
|
71
71
|
"@opentelemetry/api": "^1.9.1",
|
|
72
72
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
73
73
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -14,8 +14,13 @@ import type { AssistantMessageEventStream } from "jeopi-ai/utils/event-stream";
|
|
|
14
14
|
import { buildModel } from "jeopi-catalog/build";
|
|
15
15
|
import { isVertexExpressOpenAIUrl } from "jeopi-catalog/hosts";
|
|
16
16
|
import { readModelCache } from "jeopi-catalog/model-cache";
|
|
17
|
-
import {
|
|
18
|
-
|
|
17
|
+
import {
|
|
18
|
+
createModelManager,
|
|
19
|
+
type ModelManagerOptions,
|
|
20
|
+
type ModelRefreshStrategy,
|
|
21
|
+
restoreCachedModelHeaders,
|
|
22
|
+
} from "jeopi-catalog/model-manager";
|
|
23
|
+
import { type GeneratedProvider, getBundledModels, getBundledProviders } from "jeopi-catalog/models";
|
|
19
24
|
import {
|
|
20
25
|
googleAntigravityModelManagerOptions,
|
|
21
26
|
googleGeminiCliModelManagerOptions,
|
|
@@ -940,6 +945,9 @@ export class ModelRegistry {
|
|
|
940
945
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
941
946
|
let builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
942
947
|
const cachedStandardResult = this.#loadCachedStandardProviderModels();
|
|
948
|
+
builtInModels = builtInModels.filter(
|
|
949
|
+
model => !cachedStandardResult.unresolvedModelIdsByProvider.get(model.provider)?.has(model.id),
|
|
950
|
+
);
|
|
943
951
|
const cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
|
|
944
952
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
945
953
|
// Only drop bundled fallback models when the cached project-catalog row is
|
|
@@ -1028,6 +1036,24 @@ export class ModelRegistry {
|
|
|
1028
1036
|
}
|
|
1029
1037
|
|
|
1030
1038
|
#resolveStartupModelCacheProviderId(providerId: string): string {
|
|
1039
|
+
if (providerId === "openai" || providerId === "openai-codex") {
|
|
1040
|
+
const commandKey = this.#resolveCommandBackedApiKey(providerId);
|
|
1041
|
+
const apiKey = commandKey.configured ? commandKey.value : this.authStorage.getModelCacheIdentity(providerId);
|
|
1042
|
+
const baseUrl =
|
|
1043
|
+
this.#runtimeProviderOverrides.get(providerId)?.baseUrl ??
|
|
1044
|
+
this.#providerOverrides.get(providerId)?.baseUrl ??
|
|
1045
|
+
this.getProviderBaseUrl(providerId);
|
|
1046
|
+
if (providerId === "openai-codex") {
|
|
1047
|
+
return (
|
|
1048
|
+
openaiCodexModelManagerOptions({
|
|
1049
|
+
accessToken: apiKey,
|
|
1050
|
+
baseUrl,
|
|
1051
|
+
}).cacheProviderId ?? providerId
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
|
|
1055
|
+
return descriptor?.createModelManagerOptions({ apiKey, baseUrl }).cacheProviderId ?? providerId;
|
|
1056
|
+
}
|
|
1031
1057
|
const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
|
|
1032
1058
|
if (!descriptor) {
|
|
1033
1059
|
return providerId;
|
|
@@ -1039,10 +1065,22 @@ export class ModelRegistry {
|
|
|
1039
1065
|
return descriptor.createModelManagerOptions({ baseUrl, fetch: this.#fetch }).cacheProviderId ?? providerId;
|
|
1040
1066
|
}
|
|
1041
1067
|
|
|
1042
|
-
#
|
|
1068
|
+
#restoreConfiguredModelHeaders(model: Model<Api>): Record<string, string> | undefined {
|
|
1069
|
+
const override = this.#providerOverrides.get(model.provider);
|
|
1070
|
+
const withoutHeaders = { ...model, headers: undefined };
|
|
1071
|
+
const configured = override ? this.#applyProviderTransportOverride(withoutHeaders, override) : withoutHeaders;
|
|
1072
|
+
return this.#applyProviderModelOverrides(model.provider, [configured])[0]?.headers;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
#loadCachedStandardProviderModels(): {
|
|
1076
|
+
models: Model<Api>[];
|
|
1077
|
+
authoritativeFreshProviders: Set<string>;
|
|
1078
|
+
unresolvedModelIdsByProvider: Map<string, ReadonlySet<string>>;
|
|
1079
|
+
} {
|
|
1043
1080
|
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
|
|
1044
1081
|
const cachedModels: Model<Api>[] = [];
|
|
1045
1082
|
const authoritativeFreshProviders = new Set<string>();
|
|
1083
|
+
const unresolvedModelIdsByProvider = new Map<string, ReadonlySet<string>>();
|
|
1046
1084
|
for (const providerId of STARTUP_MODEL_CACHE_PROVIDER_IDS) {
|
|
1047
1085
|
if (configuredDiscoveryProviders.has(providerId)) {
|
|
1048
1086
|
continue;
|
|
@@ -1052,12 +1090,24 @@ export class ModelRegistry {
|
|
|
1052
1090
|
if (!cache) {
|
|
1053
1091
|
continue;
|
|
1054
1092
|
}
|
|
1055
|
-
|
|
1093
|
+
const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
|
|
1094
|
+
const restored = restoreCachedModelHeaders(
|
|
1095
|
+
cache.models,
|
|
1096
|
+
getBundledModels(providerId as GeneratedProvider),
|
|
1097
|
+
cache.headerOmittedModelIds,
|
|
1098
|
+
cache.unrestorableHeaderModelIds,
|
|
1099
|
+
descriptor?.createModelManagerOptions({}).restorableHeaderFallback,
|
|
1100
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1101
|
+
);
|
|
1102
|
+
if (restored.unresolvedModelIds.size > 0) {
|
|
1103
|
+
unresolvedModelIdsByProvider.set(providerId, restored.unresolvedModelIds);
|
|
1104
|
+
}
|
|
1105
|
+
if (cache.fresh && cache.authoritative && restored.unresolvedModelIds.size === 0) {
|
|
1056
1106
|
authoritativeFreshProviders.add(providerId);
|
|
1057
1107
|
}
|
|
1058
|
-
const models =
|
|
1059
|
-
|
|
1060
|
-
|
|
1108
|
+
const models = restored.models
|
|
1109
|
+
.filter(model => !restored.unresolvedModelIds.has(model.id))
|
|
1110
|
+
.map(model => (model.provider === providerId ? model : { ...model, provider: providerId }));
|
|
1061
1111
|
const providerOverride = this.#providerOverrides.get(providerId);
|
|
1062
1112
|
const withTransport = providerOverride
|
|
1063
1113
|
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
@@ -1072,7 +1122,7 @@ export class ModelRegistry {
|
|
|
1072
1122
|
: withTransport.map(model => buildModel(model));
|
|
1073
1123
|
cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
|
|
1074
1124
|
}
|
|
1075
|
-
return { models: cachedModels, authoritativeFreshProviders };
|
|
1125
|
+
return { models: cachedModels, authoritativeFreshProviders, unresolvedModelIdsByProvider };
|
|
1076
1126
|
}
|
|
1077
1127
|
|
|
1078
1128
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
@@ -1094,6 +1144,14 @@ export class ModelRegistry {
|
|
|
1094
1144
|
});
|
|
1095
1145
|
continue;
|
|
1096
1146
|
}
|
|
1147
|
+
const restored = restoreCachedModelHeaders(
|
|
1148
|
+
cache.models,
|
|
1149
|
+
[],
|
|
1150
|
+
cache.headerOmittedModelIds,
|
|
1151
|
+
cache.unrestorableHeaderModelIds,
|
|
1152
|
+
undefined,
|
|
1153
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1154
|
+
);
|
|
1097
1155
|
const configStale = this.#isDiscoveryCacheOlderThanModelsConfig(cache.updatedAt);
|
|
1098
1156
|
const models = this.#applyProviderModelOverrides(
|
|
1099
1157
|
providerConfig.provider,
|
|
@@ -1101,7 +1159,7 @@ export class ModelRegistry {
|
|
|
1101
1159
|
providerConfig,
|
|
1102
1160
|
this.#applyProviderCompat(
|
|
1103
1161
|
providerConfig.compat,
|
|
1104
|
-
|
|
1162
|
+
restored.models.filter(model => !restored.unresolvedModelIds.has(model.id)),
|
|
1105
1163
|
),
|
|
1106
1164
|
),
|
|
1107
1165
|
);
|
|
@@ -1110,7 +1168,12 @@ export class ModelRegistry {
|
|
|
1110
1168
|
provider: providerConfig.provider,
|
|
1111
1169
|
status: "cached",
|
|
1112
1170
|
optional: providerConfig.optional ?? false,
|
|
1113
|
-
stale:
|
|
1171
|
+
stale:
|
|
1172
|
+
providerConfig.discovery.type === "llama.cpp" ||
|
|
1173
|
+
!cache.fresh ||
|
|
1174
|
+
!cache.authoritative ||
|
|
1175
|
+
configStale ||
|
|
1176
|
+
restored.unresolvedModelIds.size > 0,
|
|
1114
1177
|
fetchedAt: cache.updatedAt,
|
|
1115
1178
|
models: models.map(model => model.id),
|
|
1116
1179
|
});
|
|
@@ -1126,14 +1189,18 @@ export class ModelRegistry {
|
|
|
1126
1189
|
}
|
|
1127
1190
|
|
|
1128
1191
|
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
1192
|
+
const override = this.#providerOverrides.get(providerConfig.provider);
|
|
1193
|
+
const withTransport = override
|
|
1194
|
+
? models.map(model => this.#applyProviderTransportOverride(model, override))
|
|
1195
|
+
: models;
|
|
1129
1196
|
const withDecoderMetadata =
|
|
1130
1197
|
providerConfig.discovery.type === "ollama" ||
|
|
1131
1198
|
providerConfig.discovery.type === "llama.cpp" ||
|
|
1132
1199
|
providerConfig.discovery.type === "lm-studio"
|
|
1133
|
-
?
|
|
1200
|
+
? withTransport.map(model =>
|
|
1134
1201
|
buildModel({ ...model, imageInputDecoder: "stb", compat: model.compatConfig } as ModelSpec<Api>),
|
|
1135
1202
|
)
|
|
1136
|
-
:
|
|
1203
|
+
: withTransport;
|
|
1137
1204
|
|
|
1138
1205
|
const withRemoteCompaction = providerConfig.remoteCompaction
|
|
1139
1206
|
? withDecoderMetadata.map(model =>
|
|
@@ -1412,6 +1479,15 @@ export class ModelRegistry {
|
|
|
1412
1479
|
): Promise<Model<Api>[]> {
|
|
1413
1480
|
const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
|
|
1414
1481
|
const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1482
|
+
const restored = restoreCachedModelHeaders(
|
|
1483
|
+
cached?.models ?? [],
|
|
1484
|
+
[],
|
|
1485
|
+
cached?.headerOmittedModelIds ?? [],
|
|
1486
|
+
cached?.unrestorableHeaderModelIds ?? [],
|
|
1487
|
+
undefined,
|
|
1488
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1489
|
+
);
|
|
1490
|
+
const usableCachedModels = restored.models.filter(model => !restored.unresolvedModelIds.has(model.id));
|
|
1415
1491
|
const cacheOlderThanConfig = cached !== null && this.#isDiscoveryCacheOlderThanModelsConfig(cached.updatedAt);
|
|
1416
1492
|
const shouldForceOnlineDiscovery =
|
|
1417
1493
|
strategy === "online-if-uncached" && (cacheOlderThanConfig || isAuthoritativeLocalDiscovery(providerConfig));
|
|
@@ -1426,15 +1502,10 @@ export class ModelRegistry {
|
|
|
1426
1502
|
optional: providerConfig.optional ?? false,
|
|
1427
1503
|
stale: cached !== null,
|
|
1428
1504
|
fetchedAt: cached?.updatedAt,
|
|
1429
|
-
models:
|
|
1505
|
+
models: usableCachedModels.map(model => model.id),
|
|
1430
1506
|
});
|
|
1431
1507
|
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
1432
|
-
return cached
|
|
1433
|
-
? this.#normalizeDiscoverableModels(
|
|
1434
|
-
providerConfig,
|
|
1435
|
-
cached.models.map(model => buildModel(model)),
|
|
1436
|
-
)
|
|
1437
|
-
: [];
|
|
1508
|
+
return cached ? this.#normalizeDiscoverableModels(providerConfig, usableCachedModels) : [];
|
|
1438
1509
|
}
|
|
1439
1510
|
}
|
|
1440
1511
|
|
|
@@ -1444,7 +1515,10 @@ export class ModelRegistry {
|
|
|
1444
1515
|
try {
|
|
1445
1516
|
const models = this.#applyProviderModelOverrides(
|
|
1446
1517
|
providerId,
|
|
1447
|
-
|
|
1518
|
+
this.#normalizeDiscoverableModels(
|
|
1519
|
+
providerConfig,
|
|
1520
|
+
await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
|
|
1521
|
+
),
|
|
1448
1522
|
);
|
|
1449
1523
|
this.#lastDiscoveryWarnings.delete(providerId);
|
|
1450
1524
|
return models.map(toModelSpec);
|
|
@@ -1461,6 +1535,7 @@ export class ModelRegistry {
|
|
|
1461
1535
|
cacheProviderId,
|
|
1462
1536
|
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
1463
1537
|
fetchDynamicModels,
|
|
1538
|
+
restoreCachedHeaders: model => this.#restoreConfiguredModelHeaders(model),
|
|
1464
1539
|
});
|
|
1465
1540
|
const result = await manager.refresh(effectiveStrategy);
|
|
1466
1541
|
const status = discoveryError
|
|
@@ -1594,6 +1669,8 @@ export class ModelRegistry {
|
|
|
1594
1669
|
return openaiCodexModelManagerOptions({
|
|
1595
1670
|
accessToken,
|
|
1596
1671
|
accountId,
|
|
1672
|
+
baseUrl: this.getProviderBaseUrl("openai-codex"),
|
|
1673
|
+
fetch: this.#fetch,
|
|
1597
1674
|
});
|
|
1598
1675
|
},
|
|
1599
1676
|
},
|
|
@@ -1645,6 +1722,11 @@ export class ModelRegistry {
|
|
|
1645
1722
|
}
|
|
1646
1723
|
options.push(descriptor.createOptions(key));
|
|
1647
1724
|
}
|
|
1725
|
+
for (const option of options) {
|
|
1726
|
+
if (option.providerId === "openai" || option.providerId === "openai-codex") {
|
|
1727
|
+
option.cacheProviderId = this.#resolveStartupModelCacheProviderId(option.providerId);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1648
1730
|
// Append runtime model managers registered by extensions via fetchDynamicModels.
|
|
1649
1731
|
for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
|
|
1650
1732
|
options.push(managerOpts);
|
|
@@ -726,7 +726,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
726
726
|
);
|
|
727
727
|
|
|
728
728
|
// Fast path: reuse Markdown children when shape is stable during streaming
|
|
729
|
-
if (this.#tryFastPathUpdate(message)) return;
|
|
729
|
+
if (this.#tryFastPathUpdate(message, opts)) return;
|
|
730
730
|
|
|
731
731
|
// Clear content container
|
|
732
732
|
this.#contentContainer.clear();
|
|
@@ -215,6 +215,7 @@ export class StreamingRevealController {
|
|
|
215
215
|
#component: StreamingRevealComponent | undefined;
|
|
216
216
|
#timer: NodeJS.Timeout | undefined;
|
|
217
217
|
#revealed = 0;
|
|
218
|
+
#targetDirty = false;
|
|
218
219
|
#hideThinkingBlock = false;
|
|
219
220
|
#proseOnlyThinking = true;
|
|
220
221
|
#smoothStreaming = true;
|
|
@@ -275,6 +276,9 @@ export class StreamingRevealController {
|
|
|
275
276
|
if (!this.#component) return;
|
|
276
277
|
if (!this.#smoothStreaming) {
|
|
277
278
|
const total = this.#visibleUnits(message);
|
|
279
|
+
this.#revealed = total;
|
|
280
|
+
this.#targetDirty = false;
|
|
281
|
+
this.#stopTimer();
|
|
278
282
|
this.#component.updateContent(this.#build(message, total), { transient: true });
|
|
279
283
|
return;
|
|
280
284
|
}
|
|
@@ -283,6 +287,7 @@ export class StreamingRevealController {
|
|
|
283
287
|
// A tool call is a transcript-order boundary: finish any leading
|
|
284
288
|
// assistant text before EventController renders the separate tool card.
|
|
285
289
|
this.#revealed = total;
|
|
290
|
+
this.#targetDirty = false;
|
|
286
291
|
this.#stopTimer();
|
|
287
292
|
this.#component.updateContent(this.#build(message, this.#revealed), {
|
|
288
293
|
transient: true,
|
|
@@ -292,8 +297,15 @@ export class StreamingRevealController {
|
|
|
292
297
|
if (this.#revealed > total) {
|
|
293
298
|
this.#revealed = total;
|
|
294
299
|
}
|
|
295
|
-
this.#
|
|
296
|
-
|
|
300
|
+
if (this.#revealed < total) {
|
|
301
|
+
// ponytail: reuse the reveal timer; only add another scheduler if streaming needs a separate cadence.
|
|
302
|
+
this.#targetDirty = false;
|
|
303
|
+
this.#syncTimer(total);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Caught-up rewrites still need one render of the newest target at the next tick.
|
|
307
|
+
this.#startTimer();
|
|
308
|
+
this.#targetDirty = true;
|
|
297
309
|
}
|
|
298
310
|
|
|
299
311
|
stop(): void {
|
|
@@ -301,6 +313,7 @@ export class StreamingRevealController {
|
|
|
301
313
|
this.#target = undefined;
|
|
302
314
|
this.#component = undefined;
|
|
303
315
|
this.#revealed = 0;
|
|
316
|
+
this.#targetDirty = false;
|
|
304
317
|
this.#unitCounter.reset();
|
|
305
318
|
}
|
|
306
319
|
|
|
@@ -377,6 +390,12 @@ export class StreamingRevealController {
|
|
|
377
390
|
}
|
|
378
391
|
const total = this.#visibleUnits(target);
|
|
379
392
|
if (this.#revealed >= total) {
|
|
393
|
+
if (this.#targetDirty) {
|
|
394
|
+
this.#targetDirty = false;
|
|
395
|
+
this.#revealed = total;
|
|
396
|
+
this.#renderCurrent();
|
|
397
|
+
this.#requestRender();
|
|
398
|
+
}
|
|
380
399
|
this.#stopTimer();
|
|
381
400
|
return;
|
|
382
401
|
}
|
|
@@ -59,9 +59,14 @@ function shouldExternalizeImagePayload(
|
|
|
59
59
|
return (key === TEXT_CONTENT_KEY && isImageBlock(value)) || key === "images";
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Signature and encrypted fields bind their carrier to its exact bytes. */
|
|
63
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
64
|
+
return typeof value === "string" && value.length > 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
/**
|
|
63
68
|
* Recursively truncate large strings in an object for session persistence.
|
|
64
|
-
* - Truncates
|
|
69
|
+
* - Truncates oversized strings except signed/encrypted blocks and signature keys
|
|
65
70
|
* - Externalizes oversized image payloads to blob refs
|
|
66
71
|
* - Updates lineCount when content is truncated
|
|
67
72
|
* - Returns original object if no changes needed (structural sharing)
|
|
@@ -76,16 +81,29 @@ function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string
|
|
|
76
81
|
if (shouldExternalizeImagePayload(obj, key)) {
|
|
77
82
|
return { ...obj, data: externalizeImageDataSync(blobStore, obj.data, obj.mimeType) };
|
|
78
83
|
}
|
|
84
|
+
// Signed content and encrypted reasoning must replay byte-for-byte. Keep the
|
|
85
|
+
// whole carrier, including paired text and tool arguments; unsigned content
|
|
86
|
+
// still follows the normal size and transient-field sanitization rules.
|
|
87
|
+
if (typeof obj === "object" && "type" in obj) {
|
|
88
|
+
const signed =
|
|
89
|
+
(obj.type === "thinking" && "thinkingSignature" in obj && isNonEmptyString(obj.thinkingSignature)) ||
|
|
90
|
+
(obj.type === "text" && "textSignature" in obj && isNonEmptyString(obj.textSignature)) ||
|
|
91
|
+
(obj.type === "toolCall" && "thoughtSignature" in obj && isNonEmptyString(obj.thoughtSignature));
|
|
92
|
+
const redacted = obj.type === "redactedThinking" && "data" in obj && isNonEmptyString(obj.data);
|
|
93
|
+
const encryptedReasoning =
|
|
94
|
+
obj.type === "reasoning" && "encrypted_content" in obj && isNonEmptyString(obj.encrypted_content);
|
|
95
|
+
if (signed || redacted || encryptedReasoning) return obj;
|
|
96
|
+
}
|
|
79
97
|
|
|
80
98
|
if (typeof obj === "string") {
|
|
81
99
|
if (key === "image_url" && isImageDataUrl(obj)) {
|
|
82
100
|
return externalizeImageDataUrlSync(blobStore, obj);
|
|
83
101
|
}
|
|
84
102
|
if (obj.length > MAX_PERSIST_CHARS) {
|
|
85
|
-
//
|
|
86
|
-
//
|
|
103
|
+
// Unknown carrier shapes must retain opaque signatures too: clearing or
|
|
104
|
+
// truncating one loses provider context or makes replay invalid.
|
|
87
105
|
if (key === "thinkingSignature" || key === "thoughtSignature" || key === "textSignature") {
|
|
88
|
-
return
|
|
106
|
+
return obj;
|
|
89
107
|
}
|
|
90
108
|
const limit = Math.max(0, MAX_PERSIST_CHARS - TRUNCATION_NOTICE.length);
|
|
91
109
|
return `${truncateString(obj, limit)}${TRUNCATION_NOTICE}`;
|
|
@@ -35,15 +35,24 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
35
35
|
|
|
36
36
|
export type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number];
|
|
37
37
|
|
|
38
|
+
export const HIDDEN_TOOL_NAMES = ["yield", "report_finding", "report_tool_issue", "resolve", "goal"] as const;
|
|
39
|
+
|
|
38
40
|
const LEGACY_BUILTIN_TOOL_NAME_ALIASES: ReadonlyMap<string, BuiltinToolName> = new Map([
|
|
39
41
|
["search", "grep"],
|
|
40
42
|
["find", "glob"],
|
|
41
43
|
]);
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
const CANONICAL_TOOL_NAMES: Record<string, true> = Object.fromEntries(
|
|
46
|
+
[...BUILTIN_TOOL_NAMES, ...HIDDEN_TOOL_NAMES].map(name => [name, true]),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/** Canonicalize built-in IDs and legacy aliases. Leave plugin names unchanged. */
|
|
44
50
|
export function normalizeToolName(name: string): string {
|
|
45
51
|
const normalized = name.toLowerCase();
|
|
46
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
LEGACY_BUILTIN_TOOL_NAME_ALIASES.get(normalized) ??
|
|
54
|
+
(Object.hasOwn(CANONICAL_TOOL_NAMES, normalized) ? normalized : name)
|
|
55
|
+
);
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/** Normalize and deduplicate tool names while preserving first-seen order. */
|
package/src/tools/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ import { AstEditTool } from "./ast-edit";
|
|
|
40
40
|
import { AstGrepTool } from "./ast-grep";
|
|
41
41
|
import { BashTool } from "./bash";
|
|
42
42
|
import { BrowserTool } from "./browser";
|
|
43
|
-
import { type BuiltinToolName, normalizeToolNames } from "./builtin-names";
|
|
43
|
+
import { type BuiltinToolName, type HIDDEN_TOOL_NAMES, normalizeToolNames } from "./builtin-names";
|
|
44
44
|
import { type CheckpointState, CheckpointTool, type CompletedRewindState, RewindTool } from "./checkpoint";
|
|
45
45
|
import { DebugTool } from "./debug";
|
|
46
46
|
import { EvalTool } from "./eval";
|
|
@@ -494,7 +494,7 @@ export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
|
494
494
|
report_tool_issue: s => createReportToolIssueTool(s),
|
|
495
495
|
resolve: s => new ResolveTool(s),
|
|
496
496
|
goal: s => new GoalTool(s),
|
|
497
|
-
}
|
|
497
|
+
} satisfies Record<(typeof HIDDEN_TOOL_NAMES)[number], ToolFactory>;
|
|
498
498
|
|
|
499
499
|
export type ToolName = BuiltinToolName;
|
|
500
500
|
|
|
@@ -44,17 +44,12 @@ function getDefaultModelCandidates(): string[] {
|
|
|
44
44
|
const bundledModels = getBundledModels("openai-codex");
|
|
45
45
|
const bundledIds = new Set(bundledModels.map(model => model.id));
|
|
46
46
|
const candidates = DEFAULT_MODEL_PREFERENCES.filter(modelId => bundledIds.has(modelId));
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const nonMini = bundledModels.find(model => !model.id.includes("mini") && !model.id.includes("spark"));
|
|
53
|
-
if (nonMini) {
|
|
54
|
-
return [nonMini.id];
|
|
47
|
+
for (const model of bundledModels) {
|
|
48
|
+
if (/^gpt-\d/.test(model.id) && !candidates.includes(model.id)) {
|
|
49
|
+
candidates.push(model.id);
|
|
50
|
+
}
|
|
55
51
|
}
|
|
56
|
-
|
|
57
|
-
return bundledModels[0]?.id ? [bundledModels[0].id] : [FALLBACK_MODEL];
|
|
52
|
+
return candidates.length > 0 ? candidates : [FALLBACK_MODEL];
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
function shouldRetryWithNextDefaultModel(error: unknown): boolean {
|
|
@@ -489,8 +484,8 @@ async function callCodexSearch(
|
|
|
489
484
|
* Default-model behavior:
|
|
490
485
|
* - If `PI_CODEX_WEB_SEARCH_MODEL` is set, use it exactly once and surface any
|
|
491
486
|
* upstream error verbatim.
|
|
492
|
-
* - Otherwise prefer
|
|
493
|
-
*
|
|
487
|
+
* - Otherwise prefer known bundled defaults, then include newly cataloged GPT
|
|
488
|
+
* models, and retry the next candidate only when Codex returns the
|
|
494
489
|
* known 400 "model is not supported" family. This avoids selecting
|
|
495
490
|
* `gpt-5-codex-mini` first on ChatGPT accounts, which OpenAI rejects.
|
|
496
491
|
*/
|