@oh-my-pi/pi-coding-agent 17.1.1 → 17.1.3
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 +40 -0
- package/dist/cli.js +6317 -4085
- package/dist/types/cli/bench-cli.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +6 -3
- package/dist/types/config/settings-schema.d.ts +4 -0
- package/dist/types/launch/broker-list-order.test.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -6
- package/dist/types/modes/types.d.ts +8 -5
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/task/structured-subagent.d.ts +3 -0
- package/dist/types/task/types.d.ts +11 -11
- package/dist/types/thinking.d.ts +13 -0
- package/dist/types/tools/auto-generated-guard.d.ts +5 -2
- package/dist/types/tools/eval-format/index.d.ts +7 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/julia.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval-format/ruby.d.ts +2 -0
- package/dist/types/tools/memory-render.d.ts +1 -4
- package/dist/types/tools/resolve.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +4 -1
- package/dist/types/web/search/providers/base.d.ts +16 -0
- package/dist/types/web/search/providers/brave.d.ts +2 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
- package/dist/types/web/search/providers/gemini.d.ts +3 -0
- package/dist/types/web/search/providers/jina.d.ts +2 -0
- package/dist/types/web/search/providers/kagi.d.ts +2 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/parallel.d.ts +2 -0
- package/dist/types/web/search/providers/perplexity.d.ts +3 -0
- package/dist/types/web/search/providers/searxng.d.ts +10 -0
- package/dist/types/web/search/providers/tavily.d.ts +8 -0
- package/dist/types/web/search/query.d.ts +190 -0
- package/package.json +12 -12
- package/src/cli/bench-cli.ts +12 -1
- package/src/cli/web-search-cli.ts +7 -0
- package/src/config/model-resolver.ts +17 -6
- package/src/config/settings-schema.ts +5 -0
- package/src/debug/raw-sse-buffer.ts +157 -30
- package/src/edit/hashline/filesystem.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/export/share.ts +4 -3
- package/src/launch/broker-list-order.test.ts +89 -0
- package/src/launch/broker.ts +49 -8
- package/src/modes/controllers/input-controller.ts +8 -8
- package/src/modes/interactive-mode.ts +60 -5
- package/src/modes/types.ts +9 -4
- package/src/modes/utils/ui-helpers.ts +7 -8
- package/src/prompts/system/resolve-device-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +2 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/task.md +5 -2
- package/src/prompts/tools/web-search.md +2 -0
- package/src/sdk.ts +8 -4
- package/src/session/agent-session.ts +14 -0
- package/src/session/queued-messages.ts +7 -1
- package/src/session/stream-guards.ts +1 -1
- package/src/task/executor.ts +17 -9
- package/src/task/index.ts +14 -34
- package/src/task/structured-subagent.ts +4 -0
- package/src/task/types.ts +15 -13
- package/src/thinking.ts +27 -0
- package/src/tools/ast-edit.ts +4 -1
- package/src/tools/auto-generated-guard.ts +18 -5
- package/src/tools/bash.ts +13 -0
- package/src/tools/browser/render.ts +2 -1
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +12 -6
- package/src/tools/memory-render.ts +11 -2
- package/src/tools/render-utils.ts +8 -3
- package/src/tools/resolve.ts +10 -1
- package/src/tools/todo.ts +58 -6
- package/src/tools/write.ts +1 -1
- package/src/web/search/index.ts +28 -5
- package/src/web/search/providers/anthropic.ts +62 -4
- package/src/web/search/providers/base.ts +16 -0
- package/src/web/search/providers/brave.ts +30 -3
- package/src/web/search/providers/codex.ts +14 -1
- package/src/web/search/providers/duckduckgo.ts +23 -1
- package/src/web/search/providers/ecosia.ts +5 -1
- package/src/web/search/providers/exa.ts +28 -1
- package/src/web/search/providers/firecrawl.ts +37 -3
- package/src/web/search/providers/gemini.ts +12 -2
- package/src/web/search/providers/google.ts +2 -1
- package/src/web/search/providers/jina.ts +31 -8
- package/src/web/search/providers/kagi.ts +10 -1
- package/src/web/search/providers/kimi.ts +16 -1
- package/src/web/search/providers/mojeek.ts +14 -1
- package/src/web/search/providers/parallel.ts +48 -2
- package/src/web/search/providers/perplexity.ts +94 -6
- package/src/web/search/providers/searxng.ts +145 -9
- package/src/web/search/providers/startpage.ts +8 -2
- package/src/web/search/providers/synthetic.ts +7 -1
- package/src/web/search/providers/tavily.ts +52 -3
- package/src/web/search/providers/tinyfish.ts +6 -1
- package/src/web/search/providers/xai.ts +49 -2
- package/src/web/search/providers/zai.ts +19 -1
- package/src/web/search/query.ts +850 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured web-search query parsing.
|
|
3
|
+
*
|
|
4
|
+
* Agents habitually embed Google-style directives in search queries —
|
|
5
|
+
* `site:`, `before:`/`after:`, `inurl:`, `filetype:`, quoted phrases, `OR`
|
|
6
|
+
* groups, `-exclusions` — regardless of whether the backing engine parses
|
|
7
|
+
* them. This module turns a raw query into a {@link StructuredQuery} so each
|
|
8
|
+
* provider can:
|
|
9
|
+
*
|
|
10
|
+
* 1. map constraints onto native API parameters where they exist (Perplexity
|
|
11
|
+
* `search_domain_filter`, Tavily `include_domains`, Exa date bounds, …),
|
|
12
|
+
* 2. rebuild a query string containing only the syntax the target engine
|
|
13
|
+
* understands ({@link formatQuery}), and
|
|
14
|
+
* 3. post-filter returned sources leniently ({@link applyQueryConstraints}):
|
|
15
|
+
* a constraint dimension that would eliminate every result is dropped and
|
|
16
|
+
* reported rather than returning nothing.
|
|
17
|
+
*/
|
|
18
|
+
import type { SearchSource } from "./types.js";
|
|
19
|
+
/** One free-text token of the query (everything that is not a recognized directive). */
|
|
20
|
+
export interface QueryTerm {
|
|
21
|
+
/** Term text without quotes or operator prefixes. */
|
|
22
|
+
text: string;
|
|
23
|
+
/** Quoted exact phrase (`"like this"`) or verbatim-required (`+term`). */
|
|
24
|
+
phrase?: boolean;
|
|
25
|
+
/** Excluded via `-term` or `NOT term`. */
|
|
26
|
+
negated?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* OR-group id. Terms sharing an id are alternatives (`a OR b`); terms
|
|
29
|
+
* without a group are implicitly AND-ed. Groups are always contiguous
|
|
30
|
+
* runs in {@link StructuredQuery.terms}.
|
|
31
|
+
*/
|
|
32
|
+
group?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A raw query decomposed into free text plus every recognized constraint.
|
|
36
|
+
*
|
|
37
|
+
* All list fields are always present (possibly empty) so consumers can map
|
|
38
|
+
* over them without null checks. Values are stored as typed by the user
|
|
39
|
+
* except for normalization noted per field.
|
|
40
|
+
*/
|
|
41
|
+
export interface StructuredQuery {
|
|
42
|
+
/** Original query string, verbatim. */
|
|
43
|
+
raw: string;
|
|
44
|
+
/**
|
|
45
|
+
* Free-text remainder with all recognized directives removed; phrases
|
|
46
|
+
* stay quoted, exclusions keep `-`, OR groups keep `OR`. Empty when the
|
|
47
|
+
* query was directives only — use {@link formatQuery} for a never-empty
|
|
48
|
+
* engine query.
|
|
49
|
+
*/
|
|
50
|
+
text: string;
|
|
51
|
+
/** Ordered free-text terms (phrases, exclusions, OR groups). */
|
|
52
|
+
terms: QueryTerm[];
|
|
53
|
+
/** `site:`/`domain:`/`host:` includes — any-of. Lowercased, scheme stripped, may carry a path (`github.com/anthropics`). */
|
|
54
|
+
sites: string[];
|
|
55
|
+
/** `-site:` exclusions, same normalization as {@link sites}. */
|
|
56
|
+
excludedSites: string[];
|
|
57
|
+
/** `inurl:`/`url:`/`allinurl:` substrings — all must appear in the URL. */
|
|
58
|
+
inUrl: string[];
|
|
59
|
+
/** `-inurl:` substrings — none may appear in the URL. */
|
|
60
|
+
excludedInUrl: string[];
|
|
61
|
+
/** `intitle:`/`title:`/`allintitle:` substrings — all must appear in the title. */
|
|
62
|
+
inTitle: string[];
|
|
63
|
+
/** `-intitle:` substrings — none may appear in the title. */
|
|
64
|
+
excludedInTitle: string[];
|
|
65
|
+
/** `intext:`/`inbody:`/`inanchor:`/`allintext:` body substrings. Not post-filterable (snippets are partial); query-building only. */
|
|
66
|
+
inText: string[];
|
|
67
|
+
/** `-intext:` body exclusions. Query-building only. */
|
|
68
|
+
excludedInText: string[];
|
|
69
|
+
/** `filetype:`/`ext:` extensions — any-of. Lowercased, no leading dot. */
|
|
70
|
+
filetypes: string[];
|
|
71
|
+
/** `-filetype:`/`-ext:` extensions — none may match. */
|
|
72
|
+
excludedFiletypes: string[];
|
|
73
|
+
/** Inclusive lower publish-date bound from `after:`/`since:`, ISO `YYYY-MM-DD`. */
|
|
74
|
+
after?: string;
|
|
75
|
+
/** Exclusive upper publish-date bound from `before:`/`until:`, ISO `YYYY-MM-DD`. */
|
|
76
|
+
before?: string;
|
|
77
|
+
/** Language code from `lang:`/`language:`, lowercased (e.g. `en`, `en-us`). */
|
|
78
|
+
lang?: string;
|
|
79
|
+
/** True when any directive or boolean operator was recognized. */
|
|
80
|
+
hasDirectives: boolean;
|
|
81
|
+
/** True when any post-filterable constraint is set (sites, url/title terms, filetypes, date bounds). */
|
|
82
|
+
hasConstraints: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Query-syntax capabilities of a target engine, used by {@link formatQuery}
|
|
86
|
+
* to decide which parsed features are re-emitted as query text. Everything
|
|
87
|
+
* defaults to `false`: the zero-value produces plain keywords suitable for
|
|
88
|
+
* natural-language APIs.
|
|
89
|
+
*/
|
|
90
|
+
export interface QuerySyntax {
|
|
91
|
+
/** Emit `"quoted phrases"`. */
|
|
92
|
+
phrases?: boolean;
|
|
93
|
+
/** Emit `-term` exclusions (negated terms are dropped otherwise). */
|
|
94
|
+
negation?: boolean;
|
|
95
|
+
/** Emit `OR` between alternatives (groups are flattened to keywords otherwise). */
|
|
96
|
+
or?: boolean;
|
|
97
|
+
/** Emit `site:`/`-site:`. */
|
|
98
|
+
site?: boolean;
|
|
99
|
+
/** Emit `inurl:`/`-inurl:`. */
|
|
100
|
+
inUrl?: boolean;
|
|
101
|
+
/** Emit `intitle:`/`-intitle:`. */
|
|
102
|
+
inTitle?: boolean;
|
|
103
|
+
/** Emit `intext:`/`-intext:`. */
|
|
104
|
+
inText?: boolean;
|
|
105
|
+
/** Emit `filetype:`/`-filetype:`. */
|
|
106
|
+
filetype?: boolean;
|
|
107
|
+
/** Emit `before:`/`after:` ISO date bounds. */
|
|
108
|
+
dateRange?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/** Full Google-style syntax: engines that parse the classic operator set (Google, Startpage, Ecosia, Brave, Kagi, Mojeek, SearXNG…). */
|
|
111
|
+
export declare const GOOGLE_QUERY_SYNTAX: QuerySyntax;
|
|
112
|
+
/** Result of {@link applyQueryConstraints}. */
|
|
113
|
+
export interface ConstraintFilterResult {
|
|
114
|
+
/** Sources surviving the lenient filter — never empty when the input was non-empty. */
|
|
115
|
+
sources: SearchSource[];
|
|
116
|
+
/**
|
|
117
|
+
* Directive renderings (`site:arxiv.org`, `before:2024-01-01`, …) of the
|
|
118
|
+
* constraint dimensions that matched zero sources and were therefore
|
|
119
|
+
* relaxed instead of enforced.
|
|
120
|
+
*/
|
|
121
|
+
dropped: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse a `before:`/`after:` value into ISO `YYYY-MM-DD`.
|
|
125
|
+
* Accepts `YYYY`, `YYYY-MM`, `YYYY-MM-DD` (also `/` and `.` separators) and
|
|
126
|
+
* `MM/DD/YYYY` (day-first assumed when the first field exceeds 12).
|
|
127
|
+
* Bare years/months resolve to the first day of the period, matching
|
|
128
|
+
* Google's `after:2024` ≙ `after:2024-01-01` semantics.
|
|
129
|
+
*/
|
|
130
|
+
export declare function parseDateValue(value: string): string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Parse a raw query into a {@link StructuredQuery}.
|
|
133
|
+
*
|
|
134
|
+
* Lenient by construction: unknown `name:value` tokens (URLs, `C:\paths`,
|
|
135
|
+
* `TS2345:`, jargon) stay in the free text verbatim, and a directive with an
|
|
136
|
+
* unparseable value (`before:someday`) degrades to a plain term instead of
|
|
137
|
+
* being dropped.
|
|
138
|
+
*/
|
|
139
|
+
export declare function parseSearchQuery(raw: string): StructuredQuery;
|
|
140
|
+
/**
|
|
141
|
+
* Rebuild a query string for an engine with the given {@link QuerySyntax}.
|
|
142
|
+
*
|
|
143
|
+
* Constraints whose syntax the engine lacks are omitted (the caller maps
|
|
144
|
+
* them onto API parameters or relies on {@link applyQueryConstraints}).
|
|
145
|
+
* Never returns an empty string for a non-empty input: a directives-only
|
|
146
|
+
* query falls back to the constraint values as keywords, then to `raw` — an
|
|
147
|
+
* engine searching *something* beats an empty-query error.
|
|
148
|
+
*/
|
|
149
|
+
export declare function formatQuery(q: StructuredQuery, syntax?: QuerySyntax): string;
|
|
150
|
+
/**
|
|
151
|
+
* Build the engine query for a credential-free HTML engine (Google,
|
|
152
|
+
* Startpage, DuckDuckGo, Ecosia, Mojeek, SearXNG, and the Public Web
|
|
153
|
+
* fan-out over them).
|
|
154
|
+
*
|
|
155
|
+
* Canonicalizes directives via {@link formatQuery} with the engine's
|
|
156
|
+
* {@link QuerySyntax} (default: full Google syntax), after demoting the
|
|
157
|
+
* operators that zero-match across the scraper set: engines only match
|
|
158
|
+
* `site:` against a bare domain (a path yields zero results everywhere),
|
|
159
|
+
* and DuckDuckGo ignores `inurl:` entirely — so either operator silently
|
|
160
|
+
* empties the result set. The raw URL as a plain term matches fine, so
|
|
161
|
+
* bare-domain `site:` filters are kept while path-carrying `site:` and all
|
|
162
|
+
* `inurl:` values become plain keywords; the demotion is structural (before
|
|
163
|
+
* formatting), so OR-grouped and quoted directives are covered. Negated
|
|
164
|
+
* forms (`-site:`, `-inurl:`) pass through untouched — demoting them would
|
|
165
|
+
* invert an exclusion into a search term; the pipeline post-filter
|
|
166
|
+
* ({@link applyQueryConstraints}) enforces every demoted or unsupported
|
|
167
|
+
* constraint on the returned sources. Directive-free queries pass through
|
|
168
|
+
* byte-identical.
|
|
169
|
+
*/
|
|
170
|
+
export declare function formatScraperQuery(query: string, parsedQuery?: StructuredQuery, syntax?: QuerySyntax): string;
|
|
171
|
+
/**
|
|
172
|
+
* `site:` matcher: exact host or subdomain of `site`; when `site` carries a
|
|
173
|
+
* path (`github.com/anthropics`), the URL path must start with it.
|
|
174
|
+
*/
|
|
175
|
+
export declare function matchesSite(url: string, site: string): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Strict per-source constraint check: every filterable dimension of `q` must
|
|
178
|
+
* pass. Sources without a resolvable date pass date bounds (a missing date
|
|
179
|
+
* is not proof of violation). For custom provider flows; the standard path
|
|
180
|
+
* is {@link applyQueryConstraints}.
|
|
181
|
+
*/
|
|
182
|
+
export declare function matchesQueryConstraints(source: SearchSource, q: StructuredQuery): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Lenient post-filter: applies each constraint dimension of `q` in turn,
|
|
185
|
+
* skipping (and reporting) any dimension that would eliminate every
|
|
186
|
+
* remaining source. Guarantees a non-empty result for a non-empty input, so
|
|
187
|
+
* a mis-scoped directive degrades to unfiltered results plus a note instead
|
|
188
|
+
* of a dead search.
|
|
189
|
+
*/
|
|
190
|
+
export declare function applyQueryConstraints(sources: readonly SearchSource[], q: StructuredQuery): ConstraintFilterResult;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.3",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -53,17 +53,17 @@
|
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
54
|
"@babel/traverse": "^7.29.7",
|
|
55
55
|
"@mozilla/readability": "^0.6.0",
|
|
56
|
-
"@oh-my-pi/hashline": "17.1.
|
|
57
|
-
"@oh-my-pi/omp-stats": "17.1.
|
|
58
|
-
"@oh-my-pi/pi-agent-core": "17.1.
|
|
59
|
-
"@oh-my-pi/pi-ai": "17.1.
|
|
60
|
-
"@oh-my-pi/pi-catalog": "17.1.
|
|
61
|
-
"@oh-my-pi/pi-mnemopi": "17.1.
|
|
62
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
63
|
-
"@oh-my-pi/pi-tui": "17.1.
|
|
64
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
65
|
-
"@oh-my-pi/pi-wire": "17.1.
|
|
66
|
-
"@oh-my-pi/snapcompact": "17.1.
|
|
56
|
+
"@oh-my-pi/hashline": "17.1.3",
|
|
57
|
+
"@oh-my-pi/omp-stats": "17.1.3",
|
|
58
|
+
"@oh-my-pi/pi-agent-core": "17.1.3",
|
|
59
|
+
"@oh-my-pi/pi-ai": "17.1.3",
|
|
60
|
+
"@oh-my-pi/pi-catalog": "17.1.3",
|
|
61
|
+
"@oh-my-pi/pi-mnemopi": "17.1.3",
|
|
62
|
+
"@oh-my-pi/pi-natives": "17.1.3",
|
|
63
|
+
"@oh-my-pi/pi-tui": "17.1.3",
|
|
64
|
+
"@oh-my-pi/pi-utils": "17.1.3",
|
|
65
|
+
"@oh-my-pi/pi-wire": "17.1.3",
|
|
66
|
+
"@oh-my-pi/snapcompact": "17.1.3",
|
|
67
67
|
"@opentelemetry/api": "^1.9.1",
|
|
68
68
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
69
69
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface BenchCommandArgs {
|
|
|
76
76
|
|
|
77
77
|
export interface BenchModelRegistry {
|
|
78
78
|
getAll(): Model<Api>[];
|
|
79
|
+
getAvailable(): Model<Api>[];
|
|
79
80
|
getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
|
|
80
81
|
resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
|
|
81
82
|
hasConfiguredAuth?(model: Model<Api>): boolean;
|
|
@@ -693,7 +694,17 @@ function resolveBenchModels(
|
|
|
693
694
|
const resolved: BenchTarget[] = [];
|
|
694
695
|
const errors: string[] = [];
|
|
695
696
|
for (const selector of selectors) {
|
|
696
|
-
|
|
697
|
+
// Bench intentionally resolves against the full catalog first, then applies
|
|
698
|
+
// its own exact-id credential fallback below. Using the CLI resolver's
|
|
699
|
+
// authenticated default here would silently redirect non-equivalent bare
|
|
700
|
+
// ids and suppress the warning for equivalent cross-provider models.
|
|
701
|
+
const result = resolveCliModel({
|
|
702
|
+
cliModel: selector,
|
|
703
|
+
modelRegistry,
|
|
704
|
+
availableModels: modelRegistry.getAll(),
|
|
705
|
+
settings,
|
|
706
|
+
preferences,
|
|
707
|
+
});
|
|
697
708
|
if (result.error) {
|
|
698
709
|
errors.push(`${selector}: ${result.error}`);
|
|
699
710
|
continue;
|
|
@@ -130,8 +130,15 @@ ${chalk.bold("Options:")}
|
|
|
130
130
|
--compact Render condensed output
|
|
131
131
|
-h, --help Show this help
|
|
132
132
|
|
|
133
|
+
${chalk.bold("Query directives:")}
|
|
134
|
+
site:/-site: after:/before: (YYYY-MM-DD) inurl: intitle: filetype:
|
|
135
|
+
"exact phrase" -term OR
|
|
136
|
+
Mapped to native provider filters where available, otherwise applied as a
|
|
137
|
+
lenient post-filter (a constraint matching nothing is relaxed, not fatal).
|
|
138
|
+
|
|
133
139
|
${chalk.bold("Examples:")}
|
|
134
140
|
${APP_NAME} q --provider=exa "what's the color of the sky"
|
|
135
141
|
${APP_NAME} q --provider=brave --recency=week "latest TypeScript 5.7 changes"
|
|
142
|
+
${APP_NAME} q 'transformer scaling site:arxiv.org after:2024 -site:reddit.com'
|
|
136
143
|
`);
|
|
137
144
|
}
|
|
@@ -466,7 +466,7 @@ export interface ModelMatchPreferences {
|
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable">;
|
|
469
|
-
type CliModelRegistry = Pick<ModelRegistry, "getAll">;
|
|
469
|
+
type CliModelRegistry = Pick<ModelRegistry, "getAll" | "getAvailable">;
|
|
470
470
|
type InitialModelRegistry = Pick<ModelRegistry, "getAvailable" | "find">;
|
|
471
471
|
type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "find" | "getApiKey">;
|
|
472
472
|
|
|
@@ -1601,6 +1601,7 @@ function findExactCliModel(
|
|
|
1601
1601
|
selector: string,
|
|
1602
1602
|
allModels: Model<Api>[],
|
|
1603
1603
|
availableModels: Model<Api>[],
|
|
1604
|
+
options?: { catalogFallback?: boolean },
|
|
1604
1605
|
): Model<Api> | undefined {
|
|
1605
1606
|
// Explicit provider/id references stay authoritative against the full catalog.
|
|
1606
1607
|
const referenced = findExactModelReferenceMatch(selector, allModels);
|
|
@@ -1615,6 +1616,13 @@ function findExactCliModel(
|
|
|
1615
1616
|
model.id.toLowerCase() === lower || formatModelString(model).toLowerCase() === lower;
|
|
1616
1617
|
const preferred = availableModels.find(isFlatMatch);
|
|
1617
1618
|
if (preferred) return preferred;
|
|
1619
|
+
// The unauthenticated catalog fallback is a weak match: a bare id like
|
|
1620
|
+
// `default` collides with the bundled `cursor/default` model, which must not
|
|
1621
|
+
// shadow a configured `modelRoles.default` role the user can actually run.
|
|
1622
|
+
// Callers resolving a possible role name pass `catalogFallback: false` so the
|
|
1623
|
+
// role gets a chance first; the deferred fuzzy fallback below still recovers
|
|
1624
|
+
// the catalog id when no role matches.
|
|
1625
|
+
if (options?.catalogFallback === false) return undefined;
|
|
1618
1626
|
return availableModels === allModels ? undefined : allModels.find(isFlatMatch);
|
|
1619
1627
|
}
|
|
1620
1628
|
|
|
@@ -1635,13 +1643,16 @@ export interface ResolveCliModelResult {
|
|
|
1635
1643
|
/**
|
|
1636
1644
|
* Resolve a single model from CLI flags.
|
|
1637
1645
|
*
|
|
1638
|
-
*
|
|
1646
|
+
* Explicit `provider/id` references and authenticated bare ids take precedence
|
|
1647
|
+
* over configured role names, which in turn take precedence over an
|
|
1648
|
+
* unauthenticated catalog-only id (so a bundled `cursor/default` never shadows a
|
|
1649
|
+
* configured `modelRoles.default`).
|
|
1639
1650
|
*/
|
|
1640
1651
|
export function resolveCliModel(options: {
|
|
1641
1652
|
cliProvider?: string;
|
|
1642
1653
|
cliModel?: string;
|
|
1643
1654
|
modelRegistry: CliModelRegistry;
|
|
1644
|
-
/** Authenticated models to prefer for unqualified selectors;
|
|
1655
|
+
/** Authenticated models to prefer for unqualified selectors; defaults to the registry's authenticated set. */
|
|
1645
1656
|
availableModels?: Model<Api>[];
|
|
1646
1657
|
settings?: Settings;
|
|
1647
1658
|
preferences?: ModelMatchPreferences;
|
|
@@ -1662,7 +1673,7 @@ export function resolveCliModel(options: {
|
|
|
1662
1673
|
};
|
|
1663
1674
|
}
|
|
1664
1675
|
|
|
1665
|
-
const availableModels = preferredModels ??
|
|
1676
|
+
const availableModels = preferredModels ?? modelRegistry.getAvailable();
|
|
1666
1677
|
const providerMap = new Map<string, string>();
|
|
1667
1678
|
for (const model of allModels) {
|
|
1668
1679
|
providerMap.set(model.provider.toLowerCase(), model.provider);
|
|
@@ -1680,7 +1691,7 @@ export function resolveCliModel(options: {
|
|
|
1680
1691
|
|
|
1681
1692
|
const trimmedModel = cliModel.trim();
|
|
1682
1693
|
if (!provider) {
|
|
1683
|
-
const exact = findExactCliModel(trimmedModel, allModels, availableModels);
|
|
1694
|
+
const exact = findExactCliModel(trimmedModel, allModels, availableModels, { catalogFallback: false });
|
|
1684
1695
|
if (exact) {
|
|
1685
1696
|
return {
|
|
1686
1697
|
model: exact,
|
|
@@ -1696,7 +1707,7 @@ export function resolveCliModel(options: {
|
|
|
1696
1707
|
MAX_THINKING_SUFFIX_OPTIONS,
|
|
1697
1708
|
);
|
|
1698
1709
|
if (exactThinkingLevel) {
|
|
1699
|
-
const exactSuffixed = findExactCliModel(exactBase, allModels, availableModels);
|
|
1710
|
+
const exactSuffixed = findExactCliModel(exactBase, allModels, availableModels, { catalogFallback: false });
|
|
1700
1711
|
if (exactSuffixed) {
|
|
1701
1712
|
return {
|
|
1702
1713
|
model: exactSuffixed,
|
|
@@ -3,6 +3,13 @@ import type { Model, ProviderResponseMetadata, RawSseEvent } from "@oh-my-pi/pi-
|
|
|
3
3
|
const MAX_RAW_SSE_EVENTS = 1_000;
|
|
4
4
|
const MAX_RAW_SSE_CHARS = 512_000;
|
|
5
5
|
const MAX_RAW_SSE_EVENT_CHARS = 64_000;
|
|
6
|
+
// Reserve room for the `: omp-debug-truncated` / `: omp-debug-elided` marker
|
|
7
|
+
// lines so a trimmed event stays within MAX_RAW_SSE_EVENT_CHARS overall.
|
|
8
|
+
const TRIM_MARKER_RESERVE = 200;
|
|
9
|
+
// Caps applied to individual tool entries when compacting a `tools` array
|
|
10
|
+
// inside an oversized `data:` payload.
|
|
11
|
+
const MAX_TOOL_SCHEMA_CHARS = 200;
|
|
12
|
+
const MAX_TOOL_DESCRIPTION_CHARS = 200;
|
|
6
13
|
|
|
7
14
|
export type RawSseDebugRecord =
|
|
8
15
|
| {
|
|
@@ -45,42 +52,162 @@ export interface RawSseDebugSnapshot {
|
|
|
45
52
|
// path. The parallel array keeps records as plain monomorphic objects.
|
|
46
53
|
type TrimResult = { raw: string[]; truncated: boolean; originalChars: number; chars: number };
|
|
47
54
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// `chars` uses the historical formula `reduce(line.length + 1, init = 1)` so
|
|
56
|
+
// the accounting matches the previous `countRecordChars` byte-for-byte (the
|
|
57
|
+
// trailing +1 covers the record-level newline that `rawRecordText` appends in
|
|
58
|
+
// `toRawText`).
|
|
59
|
+
function countLines(lines: readonly string[]): number {
|
|
60
|
+
let chars = 0;
|
|
61
|
+
for (let i = 0; i < lines.length; i++) chars += lines[i].length + 1;
|
|
62
|
+
return chars;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function elideText(text: string, max: number): string {
|
|
66
|
+
if (text.length <= max) return text;
|
|
67
|
+
return `${text.slice(0, max)}… (+${text.length - max} chars)`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Shrinks one tool definition in place: schemas (`parameters` for OpenAI
|
|
71
|
+
// shapes, `input_schema` for Anthropic) become elided JSON strings and long
|
|
72
|
+
// descriptions are cut, while `name`/`type` survive untouched. Chat-completions
|
|
73
|
+
// nests the payload under `function`.
|
|
74
|
+
function compactToolEntry(tool: unknown): boolean {
|
|
75
|
+
if (typeof tool !== "object" || tool === null) return false;
|
|
76
|
+
const obj = tool as Record<string, unknown>;
|
|
77
|
+
let changed = false;
|
|
78
|
+
if (typeof obj.function === "object" && obj.function !== null) {
|
|
79
|
+
changed = compactToolEntry(obj.function);
|
|
80
|
+
}
|
|
81
|
+
for (const key of ["parameters", "input_schema"]) {
|
|
82
|
+
const schema = obj[key];
|
|
83
|
+
if (schema === undefined || schema === null) continue;
|
|
84
|
+
const text = typeof schema === "string" ? schema : JSON.stringify(schema);
|
|
85
|
+
if (text.length <= MAX_TOOL_SCHEMA_CHARS) continue;
|
|
86
|
+
obj[key] = elideText(text, MAX_TOOL_SCHEMA_CHARS);
|
|
87
|
+
changed = true;
|
|
88
|
+
}
|
|
89
|
+
if (typeof obj.description === "string" && obj.description.length > MAX_TOOL_DESCRIPTION_CHARS) {
|
|
90
|
+
obj.description = elideText(obj.description, MAX_TOOL_DESCRIPTION_CHARS);
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
return changed;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Walks a parsed SSE payload and compacts every `tools` array it finds
|
|
97
|
+
// (e.g. `response.tools` echoed back by the Responses API). Mutates `node`.
|
|
98
|
+
function compactToolsDeep(node: unknown): boolean {
|
|
99
|
+
if (Array.isArray(node)) {
|
|
100
|
+
let changed = false;
|
|
101
|
+
for (const item of node) changed = compactToolsDeep(item) || changed;
|
|
102
|
+
return changed;
|
|
103
|
+
}
|
|
104
|
+
if (typeof node !== "object" || node === null) return false;
|
|
105
|
+
let changed = false;
|
|
106
|
+
const obj = node as Record<string, unknown>;
|
|
107
|
+
for (const key in obj) {
|
|
108
|
+
const value = obj[key];
|
|
109
|
+
if (key === "tools" && Array.isArray(value)) {
|
|
110
|
+
for (const tool of value) changed = compactToolEntry(tool) || changed;
|
|
111
|
+
} else {
|
|
112
|
+
changed = compactToolsDeep(value) || changed;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return changed;
|
|
116
|
+
}
|
|
59
117
|
|
|
118
|
+
// Rewrites oversized `data:` lines with tool schemas compacted. Returns null
|
|
119
|
+
// when nothing changed (unparseable payloads or no tools to shrink). Only
|
|
120
|
+
// invoked on events that already blew the budget, so the JSON round-trip is
|
|
121
|
+
// off the streaming hot path.
|
|
122
|
+
function compactToolLines(raw: readonly string[]): string[] | null {
|
|
123
|
+
let changed = false;
|
|
124
|
+
const out = raw.map(line => {
|
|
125
|
+
if (!line.startsWith("data:") || line.length <= MAX_TOOL_SCHEMA_CHARS) return line;
|
|
126
|
+
const start = line.charCodeAt(5) === 32 ? 6 : 5;
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(line.slice(start));
|
|
129
|
+
if (!compactToolsDeep(parsed)) return line;
|
|
130
|
+
changed = true;
|
|
131
|
+
return `data: ${JSON.stringify(parsed)}`;
|
|
132
|
+
} catch {
|
|
133
|
+
return line;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return changed ? out : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Keeps the first and last portions of an over-budget event and drops the
|
|
140
|
+
// middle, so leading fields (id/model/status) AND trailing fields
|
|
141
|
+
// (usage/finish_reason) both stay visible. A `: omp-debug-elided` comment
|
|
142
|
+
// marks the cut; split lines carry `…` at the cut edge.
|
|
143
|
+
function headTailTrim(lines: string[], budget: number, elidedTotal: number): string[] {
|
|
144
|
+
const headBudget = budget >> 1;
|
|
145
|
+
const tailBudget = budget - headBudget;
|
|
146
|
+
|
|
147
|
+
let i = 0;
|
|
148
|
+
let headRemaining = headBudget;
|
|
149
|
+
const out: string[] = [];
|
|
150
|
+
while (i < lines.length && lines[i].length + 1 <= headRemaining) {
|
|
151
|
+
headRemaining -= lines[i].length + 1;
|
|
152
|
+
out.push(lines[i]);
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let j = lines.length - 1;
|
|
157
|
+
let tailRemaining = tailBudget;
|
|
158
|
+
const tail: string[] = [];
|
|
159
|
+
while (j >= i && lines[j].length + 1 <= tailRemaining) {
|
|
160
|
+
tailRemaining -= lines[j].length + 1;
|
|
161
|
+
tail.push(lines[j]);
|
|
162
|
+
j--;
|
|
163
|
+
}
|
|
164
|
+
tail.reverse();
|
|
165
|
+
|
|
166
|
+
let elided = elidedTotal - countLines(out) - countLines(tail);
|
|
167
|
+
if (i <= j) {
|
|
168
|
+
// lines[i..j] straddle the cut: keep a head slice of the first and a
|
|
169
|
+
// tail slice of the last (the same line when i === j).
|
|
170
|
+
const headSlice = lines[i].slice(0, Math.max(0, headRemaining - 2));
|
|
171
|
+
const tailStart =
|
|
172
|
+
i === j
|
|
173
|
+
? Math.max(headSlice.length, lines[j].length - tailRemaining + 2)
|
|
174
|
+
: Math.max(0, lines[j].length - tailRemaining + 2);
|
|
175
|
+
const tailSlice = lines[j].slice(tailStart);
|
|
176
|
+
elided -= headSlice.length + tailSlice.length;
|
|
177
|
+
if (headSlice.length > 0) out.push(`${headSlice}…`);
|
|
178
|
+
out.push(`: omp-debug-elided chars=${Math.max(0, elided)}`);
|
|
179
|
+
if (tailSlice.length > 0) out.push(`…${tailSlice}`);
|
|
180
|
+
} else if (elided > 0) {
|
|
181
|
+
out.push(`: omp-debug-elided chars=${elided}`);
|
|
182
|
+
}
|
|
183
|
+
out.push(...tail);
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Trim pipeline for one SSE event:
|
|
188
|
+
// 1. fits → return `raw` **by reference** (ownership contract at
|
|
189
|
+
// `RawSseDebugBuffer.recordEvent` below).
|
|
190
|
+
// 2. over budget → compact tool schemas inside `data:` JSON payloads;
|
|
191
|
+
// if that alone fits, the payload stays parseable JSON.
|
|
192
|
+
// 3. still over → head+tail trim (middle elided).
|
|
193
|
+
// Any trimmed result ends with the `: omp-debug-truncated` marker carrying
|
|
194
|
+
// the original size.
|
|
195
|
+
function trimRawLines(raw: string[]): TrimResult {
|
|
196
|
+
const originalChars = countLines(raw);
|
|
60
197
|
if (originalChars <= MAX_RAW_SSE_EVENT_CHARS) {
|
|
61
198
|
return { raw, truncated: false, originalChars, chars: originalChars + 1 };
|
|
62
199
|
}
|
|
63
200
|
|
|
64
|
-
const
|
|
65
|
-
let
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
chars += line.length + 1;
|
|
72
|
-
remaining -= line.length + 1;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const slice = line.slice(0, Math.max(0, remaining));
|
|
76
|
-
trimmed.push(slice);
|
|
77
|
-
chars += slice.length + 1;
|
|
78
|
-
remaining = 0;
|
|
201
|
+
const budget = MAX_RAW_SSE_EVENT_CHARS - TRIM_MARKER_RESERVE;
|
|
202
|
+
let lines = compactToolLines(raw) ?? raw;
|
|
203
|
+
const compactedChars = lines === raw ? originalChars : countLines(lines);
|
|
204
|
+
if (compactedChars > budget) {
|
|
205
|
+
lines = headTailTrim(lines, budget, compactedChars);
|
|
206
|
+
} else if (lines === raw) {
|
|
207
|
+
lines = raw.slice();
|
|
79
208
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
chars += tail.length + 1;
|
|
83
|
-
return { raw: trimmed, truncated: true, originalChars, chars };
|
|
209
|
+
lines.push(`: omp-debug-truncated originalChars=${originalChars}`);
|
|
210
|
+
return { raw: lines, truncated: true, originalChars, chars: countLines(lines) + 1 };
|
|
84
211
|
}
|
|
85
212
|
|
|
86
213
|
export function formatRawSseIsoTime(timestamp: number): string {
|
|
@@ -121,7 +121,7 @@ export class HashlineFilesystem extends Filesystem {
|
|
|
121
121
|
throw error;
|
|
122
122
|
}
|
|
123
123
|
// Refuse edits against generated files (lockfiles, models.json, …).
|
|
124
|
-
assertEditableFileContent(content, relativePath);
|
|
124
|
+
assertEditableFileContent(content, relativePath, this.session.settings);
|
|
125
125
|
return content;
|
|
126
126
|
}
|
|
127
127
|
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -1816,7 +1816,7 @@ export async function executePatchSingle(
|
|
|
1816
1816
|
const resolvedPath = resolvePlanPath(session, path);
|
|
1817
1817
|
const resolvedRename = rename ? resolvePlanPath(session, rename) : undefined;
|
|
1818
1818
|
|
|
1819
|
-
await assertEditableFile(resolvedPath, path);
|
|
1819
|
+
await assertEditableFile(resolvedPath, path, session.settings);
|
|
1820
1820
|
|
|
1821
1821
|
// Capture pre-edit content so we can verify the write actually hit disk.
|
|
1822
1822
|
// `LspFileSystem.writeFile` delegates to a writethrough callback that, in
|
package/src/export/share.ts
CHANGED
|
@@ -400,8 +400,9 @@ function redactShareMessage(
|
|
|
400
400
|
};
|
|
401
401
|
case "assistant":
|
|
402
402
|
// Drop opaque provider-replay state (encrypted reasoning / native history) the viewer
|
|
403
|
-
// never reads and we cannot redact field-by-field: `providerPayload
|
|
404
|
-
// `redactedThinking` blocks
|
|
403
|
+
// never reads and we cannot redact field-by-field: `providerPayload`, any
|
|
404
|
+
// `redactedThinking` blocks, and native Anthropic server-tool blocks
|
|
405
|
+
// (`server_tool_use` input / `web_search_tool_result` encrypted_content).
|
|
405
406
|
return {
|
|
406
407
|
...message,
|
|
407
408
|
providerPayload: undefined,
|
|
@@ -410,7 +411,7 @@ function redactShareMessage(
|
|
|
410
411
|
? undefined
|
|
411
412
|
: o.obfuscate(message.errorMessage, sharedRegexSecretValues),
|
|
412
413
|
content: message.content.flatMap((block): AssistantMessage["content"] => {
|
|
413
|
-
if (block.type === "redactedThinking") return [];
|
|
414
|
+
if (block.type === "redactedThinking" || block.type === "anthropicServerTool") return [];
|
|
414
415
|
if (block.type === "text") return [{ ...block, text: o.obfuscate(block.text, sharedRegexSecretValues) }];
|
|
415
416
|
if (block.type === "thinking") {
|
|
416
417
|
return [{ ...block, thinking: o.obfuscate(block.thinking, sharedRegexSecretValues) }];
|