@oh-my-pi/pi-coding-agent 13.9.2 → 13.9.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/examples/sdk/02-custom-model.ts +2 -1
  3. package/package.json +7 -7
  4. package/src/cli/args.ts +6 -5
  5. package/src/cli/list-models.ts +2 -2
  6. package/src/commands/launch.ts +3 -3
  7. package/src/config/model-registry.ts +85 -39
  8. package/src/config/model-resolver.ts +47 -21
  9. package/src/config/settings-schema.ts +56 -2
  10. package/src/discovery/helpers.ts +2 -2
  11. package/src/extensibility/custom-tools/types.ts +2 -0
  12. package/src/extensibility/extensions/loader.ts +3 -2
  13. package/src/extensibility/extensions/types.ts +10 -7
  14. package/src/extensibility/hooks/types.ts +2 -0
  15. package/src/main.ts +5 -22
  16. package/src/memories/index.ts +7 -3
  17. package/src/modes/components/footer.ts +10 -8
  18. package/src/modes/components/model-selector.ts +33 -38
  19. package/src/modes/components/settings-defs.ts +31 -2
  20. package/src/modes/components/settings-selector.ts +16 -5
  21. package/src/modes/components/status-line/context-thresholds.ts +68 -0
  22. package/src/modes/components/status-line/segments.ts +11 -12
  23. package/src/modes/components/thinking-selector.ts +7 -7
  24. package/src/modes/components/tree-selector.ts +3 -2
  25. package/src/modes/controllers/command-controller.ts +11 -26
  26. package/src/modes/controllers/event-controller.ts +16 -3
  27. package/src/modes/controllers/input-controller.ts +4 -2
  28. package/src/modes/controllers/selector-controller.ts +5 -4
  29. package/src/modes/interactive-mode.ts +2 -2
  30. package/src/modes/rpc/rpc-client.ts +5 -10
  31. package/src/modes/rpc/rpc-types.ts +5 -5
  32. package/src/modes/theme/theme.ts +8 -3
  33. package/src/priority.json +1 -0
  34. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
  35. package/src/prompts/system/system-prompt.md +18 -2
  36. package/src/prompts/tools/hashline.md +139 -83
  37. package/src/sdk.ts +22 -14
  38. package/src/session/agent-session.ts +259 -117
  39. package/src/session/agent-storage.ts +14 -14
  40. package/src/session/compaction/compaction.ts +500 -13
  41. package/src/session/messages.ts +12 -1
  42. package/src/session/session-manager.ts +77 -19
  43. package/src/slash-commands/builtin-registry.ts +48 -0
  44. package/src/task/agents.ts +3 -2
  45. package/src/task/executor.ts +2 -2
  46. package/src/task/types.ts +2 -1
  47. package/src/thinking.ts +87 -0
  48. package/src/tools/browser.ts +15 -6
  49. package/src/tools/fetch.ts +118 -100
  50. package/src/web/search/providers/exa.ts +74 -3
@@ -1,12 +1,31 @@
1
1
  Applies precise, surgical file edits by referencing `LINE#ID` tags from `read` output. Each tag uniquely identifies a line, so edits remain stable even when lines shift.
2
2
 
3
+ <critical>
4
+ - Never anchor insertions on blank lines or lone closing delimiters like `}`, `]`, `)`, `};`, or `),` — they are mechanically valid tags but semantically unstable edit boundaries.
5
+ - For `append`/`prepend`, `lines` **MUST** contain only the newly introduced content. Do not re-emit surrounding braces, brackets, parentheses, or sibling declarations that already exist in the file.
6
+ - `append`/`prepend` are for self-contained new content only: sibling declarations, new object/list members, new test cases, or similar additions whose surrounding structure stays unchanged.
7
+ - When changing existing code near a block tail or closing delimiter, default to `replace` over the owned span instead of inserting around the boundary.
8
+ - When adding a sibling declaration, default to `prepend` on the next sibling declaration instead of `append` on the previous block's closing brace.
9
+ - If any inserted line is just a closing delimiter, stop and re-check the edit shape. A closing line is only valid when it belongs to newly introduced structure; if it belongs to surrounding existing structure, your edit should be a `replace` that consumes the old boundary.
10
+ </critical>
11
+
3
12
  <workflow>
4
13
  Follow these steps in order for every edit:
5
14
  1. You **SHOULD** issue a `read` call before editing to get fresh `LINE#ID` tags. Editing without current tags causes mismatches because other edits or external changes may have shifted line numbers since your last read.
6
15
  2. You **MUST** submit one `edit` call per file with all operations. Multiple calls to the same file require re-reading between each one (tags shift after each edit), so batching avoids wasted round-trips. Think your changes through before submitting.
7
- 3. You **MUST** pick the smallest operation per change site. Each operation should be one logical mutation a single replace, insert, or delete. Combining unrelated changes into one operation makes errors harder to diagnose and recover from.
16
+ 3. You **MUST** pick the operation that matches the owning structure, not merely the smallest textual diff. Use the smallest operation only when it still cleanly owns the changed syntax. If a tiny edit would patch around a block tail, delimiter, or neighboring structural line, expand it to the semantically correct `replace` span instead.
8
17
  </workflow>
9
18
 
19
+ <checklist>
20
+ Before choosing the payload, answer these questions in order:
21
+ 1. **Am I replacing existing lines or inserting new ones?** If any existing line changes, use `replace` for the full changed span.
22
+ 2. **What declaration or block owns this anchor line?** Prefer declaration/header lines over blank lines or delimiters.
23
+ 3. **Am I inserting self-contained new content, or changing an existing block?** Use `append`/`prepend` only for self-contained additions. If surrounding code, indentation, or closers also change, use `replace`.
24
+ 4. **Am I editing near a block tail or closing delimiter?** If yes, expand the edit to own that tail instead of patching just the last line or two.
25
+ 5. **Does `lines` contain only new content?** For `append`/`prepend`, do not include existing closing braces or other surrounding syntax from the file.
26
+ 6. **Would the replacement duplicate the line immediately after `end`?** If yes, extend the range to consume the old boundary.
27
+ </checklist>
28
+
10
29
  <operations>
11
30
  **`path`** — the path to the file to edit.
12
31
  **`move`** — if set, move the file to the given path.
@@ -25,9 +44,12 @@ Tags are applied bottom-up: later edits (by position) are applied first, so earl
25
44
  </operations>
26
45
 
27
46
  <rules>
28
- 1. **Anchor on unique, structural lines.** When inserting between blocks, anchor on the nearest unique declaration using `prepend` or `append`.
29
- 2. **Use `prepend`/`append` only when the anchor line itself is not changing.** Inserting near an unchanged boundary keeps the edit minimal.
30
- 3. **Use range `replace` when any line in the span changes.** If you need to both insert lines and modify a neighboring line, a range replace covering all lines to remove is way to go.
47
+ 1. **Anchor on unique declaration or header lines, not delimiters.** Safe anchors are lines like `function beta() {`, `if (…) {`, `const value =`, or other unique structural headers. Blank lines and lone closers like `}` are never good insertion anchors.
48
+ 2. **Use `prepend`/`append` only for self-contained additions whose surrounding structure stays unchanged.** If you are adding a sibling declaration, prefer `prepend` on the next sibling declaration instead of `append` on the previous block closer.
49
+ 3. **If the change touches existing code near a block tail, use range `replace` over the owned span.** Do not patch just the final line(s) before a closing delimiter when the surrounding structure, indentation, or control flow is also changing.
50
+ 4. **Match surrounding indentation for new lines.** When inserting via `prepend`/`append`, look at the anchor line and its neighbors in the `read` output. New `lines` entries **MUST** carry the same leading whitespace. If the context uses tabs at depth 1 (`\t`), your inserted declarations need `\t` and bodies need `\t\t`. Inserting at indent level 0 inside an indented block is always wrong.
51
+ 5. **Consume the old closing boundary when your replacement emits one.** If the replacement's final line is a closing delimiter like `}`, `]`, or `)`, the `end` line **MUST** include the original matching closer that would otherwise remain in the file. Before submitting, compare the replacement's last line with the line immediately after `end`; if they would be the same boundary, extend the range so the old closer is removed.
52
+ 6. **If you expect a second tiny cleanup edit for `}`, `};`, indentation, or a duplicated boundary, your first edit shape is wrong.** Expand the first `replace` so it owns the structural tail in one shot.
31
53
  </rules>
32
54
 
33
55
  <recovery>
@@ -36,17 +58,38 @@ Edits can fail in two ways. Here is exactly what to do for each:
36
58
  2. **No-op (`identical`):** Your replacement is identical to the existing content — nothing changed. You **MUST NOT** resubmit the same edit. Re-read the target lines to understand what is actually there, then adjust your edit.
37
59
  </recovery>
38
60
 
39
- <example name="single-line replace">
61
+ <examples>
62
+ All examples below reference the same file, `util.ts`:
40
63
  ```ts
41
- {{hlinefull 23 " const timeout: number = 5000;"}}
64
+ {{hlinefull 1 "// @ts-ignore"}}
65
+ {{hlinefull 2 "const timeout = 5000;"}}
66
+ {{hlinefull 3 "const tag = \"DO NOT SHIP\";"}}
67
+ {{hlinefull 4 ""}}
68
+ {{hlinefull 5 "function alpha() {"}}
69
+ {{hlinefull 6 "\tlog();"}}
70
+ {{hlinefull 7 "}"}}
71
+ {{hlinefull 8 ""}}
72
+ {{hlinefull 9 "function beta() {"}}
73
+ {{hlinefull 10 "\t// TODO: remove after migration"}}
74
+ {{hlinefull 11 "\tlegacy();"}}
75
+ {{hlinefull 12 "\ttry {"}}
76
+ {{hlinefull 13 "\t\treturn parse(data);"}}
77
+ {{hlinefull 14 "\t} catch (err) {"}}
78
+ {{hlinefull 15 "\t\tconsole.error(err);"}}
79
+ {{hlinefull 16 "\t\treturn null;"}}
80
+ {{hlinefull 17 "\t}"}}
81
+ {{hlinefull 18 "}"}}
42
82
  ```
83
+
84
+ <example name="single-line replace">
85
+ Change the timeout from `5000` to `30_000`:
43
86
  ```
44
87
  {
45
- path: "",
88
+ path: "util.ts",
46
89
  edits: [{
47
90
  op: "replace",
48
- pos: {{hlineref 23 " const timeout: number = 5000;"}},
49
- lines: [" const timeout: number = 30_000;"]
91
+ pos: {{hlineref 2 "const timeout = 5000;"}},
92
+ lines: ["const timeout = 30_000;"]
50
93
  }]
51
94
  }
52
95
  ```
@@ -56,22 +99,22 @@ Edits can fail in two ways. Here is exactly what to do for each:
56
99
  Single line — `lines: null` deletes entirely:
57
100
  ```
58
101
  {
59
- path: "",
102
+ path: "util.ts",
60
103
  edits: [{
61
104
  op: "replace",
62
- pos: {{hlineref 7 "// @ts-ignore"}},
105
+ pos: {{hlineref 1 "// @ts-ignore"}},
63
106
  lines: null
64
107
  }]
65
108
  }
66
109
  ```
67
- Range — add `end`:
110
+ Range — remove the legacy block (lines 10–11):
68
111
  ```
69
112
  {
70
- path: "",
113
+ path: "util.ts",
71
114
  edits: [{
72
115
  op: "replace",
73
- pos: {{hlineref 80 " // TODO: remove after migration"}},
74
- end: {{hlineref 83 " }"}},
116
+ pos: {{hlineref 10 "\t// TODO: remove after migration"}},
117
+ end: {{hlineref 11 "\tlegacy();"}},
75
118
  lines: null
76
119
  }]
77
120
  }
@@ -79,15 +122,13 @@ Range — add `end`:
79
122
  </example>
80
123
 
81
124
  <example name="clear text but keep the line break">
82
- ```ts
83
- {{hlinefull 14 " placeholder: \"DO NOT SHIP\","}}
84
- ```
125
+ Blank out a line without removing it:
85
126
  ```
86
127
  {
87
- path: "",
128
+ path: "util.ts",
88
129
  edits: [{
89
130
  op: "replace",
90
- pos: {{hlineref 14 " placeholder: \"DO NOT SHIP\","}},
131
+ pos: {{hlineref 3 "const tag = \"DO NOT SHIP\";"}},
91
132
  lines: [""]
92
133
  }]
93
134
  }
@@ -95,23 +136,52 @@ Range — add `end`:
95
136
  </example>
96
137
 
97
138
  <example name="rewrite a block">
98
- ```ts
99
- {{hlinefull 60 " } catch (err) {"}}
100
- {{hlinefull 61 " console.error(err);"}}
101
- {{hlinefull 62 " return null;"}}
102
- {{hlinefull 63 " }"}}
139
+ Replace the catch body with smarter error handling:
140
+ ```
141
+ {
142
+ path: "util.ts",
143
+ edits: [{
144
+ op: "replace",
145
+ pos: {{hlineref 15 "\t\tconsole.error(err);"}},
146
+ end: {{hlineref 17 "\t}"}},
147
+ lines: [
148
+ "\t\tif (isEnoent(err)) return null;",
149
+ "\t\tthrow err;",
150
+ "\t}"
151
+ ]
152
+ }]
153
+ }
154
+ ```
155
+ </example>
156
+
157
+ <example name="own the block tail instead of patching around it">
158
+ When changing the tail of an existing block, replace the owned span instead of appending just before the closer.
159
+
160
+ Bad — appending a new return before the existing closer leaves the old tail in place and often leads to a second cleanup edit:
161
+ ```
162
+ {
163
+ path: "util.ts",
164
+ edits: [{
165
+ op: "append",
166
+ pos: {{hlineref 16 "\t\treturn null;"}},
167
+ lines: [
168
+ "\t\treturn fallback;"
169
+ ]
170
+ }]
171
+ }
103
172
  ```
173
+ Good — replace the block tail so the new logic and the closing boundary are owned by one edit:
104
174
  ```
105
175
  {
106
- path: "",
176
+ path: "util.ts",
107
177
  edits: [{
108
178
  op: "replace",
109
- pos: {{hlineref 61 " console.error(err);"}},
110
- end: {{hlineref 63 " }"}},
179
+ pos: {{hlineref 15 "\t\tconsole.error(err);"}},
180
+ end: {{hlineref 17 "\t}"}},
111
181
  lines: [
112
- " if (isEnoent(err)) return null;",
113
- " throw err;",
114
- " }"
182
+ "\t\tif (isEnoent(err)) return null;",
183
+ "\t\treturn fallback;",
184
+ "\t}"
115
185
  ]
116
186
  }]
117
187
  }
@@ -119,40 +189,35 @@ Range — add `end`:
119
189
  </example>
120
190
 
121
191
  <example name="inclusive end avoids duplicate boundary">
122
- This example demonstrates why `end` must include the original closing line when your replacement also contains that closer.
123
- ```ts
124
- {{hlinefull 70 "if (ok) {"}}
125
- {{hlinefull 71 " run();"}}
126
- {{hlinefull 72 "}"}}
127
- {{hlinefull 73 "after();"}}
128
- ```
129
- Bad — `end` stops before `}` while `lines` already includes `}`:
192
+ Simplify `beta()` to a one-liner. `end` must include the original closing `}` when the replacement also ends with `}`.
193
+
194
+ Bad `end` stops at line 17 (`\t}`), so the replacement adds `}` and the original function closer on line 18 survives. Result: two consecutive `}` lines.
130
195
  ```
131
196
  {
132
- path: "",
197
+ path: "util.ts",
133
198
  edits: [{
134
199
  op: "replace",
135
- pos: {{hlineref 70 "if (ok) {"}},
136
- end: {{hlineref 71 " run();"}},
200
+ pos: {{hlineref 9 "function beta() {"}},
201
+ end: {{hlineref 17 "\t}"}},
137
202
  lines: [
138
- "if (ok) {",
139
- " runSafe();",
203
+ "function beta() {",
204
+ "\treturn parse(data);",
140
205
  "}"
141
206
  ]
142
207
  }]
143
208
  }
144
209
  ```
145
- Good — include original `}` in the replaced range when replacement keeps `}`:
210
+ Good — include the function's own `}` on line 18 in the range, so the old closing boundary is consumed:
146
211
  ```
147
212
  {
148
- path: "",
213
+ path: "util.ts",
149
214
  edits: [{
150
215
  op: "replace",
151
- pos: {{hlineref 70 "if (ok) {"}},
152
- end: {{hlineref 72 "}"}},
216
+ pos: {{hlineref 9 "function beta() {"}},
217
+ end: {{hlineref 18 "}"}},
153
218
  lines: [
154
- "if (ok) {",
155
- " runSafe();",
219
+ "function beta() {",
220
+ "\treturn parse(data);",
156
221
  "}"
157
222
  ]
158
223
  }]
@@ -161,66 +226,54 @@ Good — include original `}` in the replaced range when replacement keeps `}`:
161
226
  </example>
162
227
 
163
228
  <example name="insert between sibling declarations">
164
- ```ts
165
- {{hlinefull 44 "function x() {"}}
166
- {{hlinefull 45 " runX();"}}
167
- {{hlinefull 46 "}"}}
168
- {{hlinefull 47 ""}}
169
- {{hlinefull 48 "function y() {"}}
170
- {{hlinefull 49 " runY();"}}
171
- {{hlinefull 50 "}"}}
172
- ```
229
+ Add a `gamma()` function between `alpha()` and `beta()`:
173
230
  ```
174
231
  {
175
- path: "",
232
+ path: "util.ts",
176
233
  edits: [{
177
234
  op: "prepend",
178
- pos: {{hlineref 48 "function y() {"}},
235
+ pos: {{hlineref 9 "function beta() {"}},
179
236
  lines: [
180
- "function z() {",
181
- " runZ();",
237
+ "function gamma() {",
238
+ "\tvalidate();",
182
239
  "}",
183
240
  ""
184
241
  ]
185
242
  }]
186
243
  }
187
244
  ```
188
- Use a trailing `""` to preserve the blank line between top-level sibling declarations.
245
+ Use a trailing `""` to preserve the blank line between sibling declarations.
189
246
  </example>
190
247
 
191
- <example name="disambiguate anchors">
192
- Blank lines and repeated patterns (`}`, `return null;`) appear many times. Always anchor on a unique line nearby instead.
193
- ```ts
194
- {{hlinefull 101 "}"}}
195
- {{hlinefull 102 ""}}
196
- {{hlinefull 103 "export function serialize(data: unknown): string {"}}
197
- ```
198
- Bad — anchoring on the blank line (ambiguous, may shift):
248
+ <example name="avoid closer anchors">
249
+ When inserting a sibling declaration, do not anchor on the previous block's lone closing brace. Anchor on the next declaration instead.
250
+
251
+ Bad appending after line 7 (`}`) happens to land in the gap today, but the anchor is still the previous function's closer rather than a stable declaration boundary:
199
252
  ```
200
253
  {
201
- path: "",
254
+ path: "util.ts",
202
255
  edits: [{
203
256
  op: "append",
204
- pos: {{hlineref 102 ""}},
257
+ pos: {{hlineref 7 "}"}},
205
258
  lines: [
206
- "function validate(data: unknown): boolean {",
207
- " return data != null && typeof data === \"object\";",
208
- "}",
209
- ""
259
+ "",
260
+ "function gamma() {",
261
+ "\tvalidate();",
262
+ "}"
210
263
  ]
211
264
  }]
212
265
  }
213
266
  ```
214
- Good — anchor on the unique declaration line:
267
+ Good — prepend before the next declaration so the new sibling is anchored on a declaration header, not a block tail:
215
268
  ```
216
269
  {
217
- path: "",
270
+ path: "util.ts",
218
271
  edits: [{
219
272
  op: "prepend",
220
- pos: {{hlineref 103 "export function serialize(data: unknown): string {"}},
273
+ pos: {{hlineref 9 "function beta() {"}},
221
274
  lines: [
222
- "function validate(data: unknown): boolean {",
223
- " return data != null && typeof data === \"object\";",
275
+ "function gamma() {",
276
+ "\tvalidate();",
224
277
  "}",
225
278
  ""
226
279
  ]
@@ -228,6 +281,7 @@ Good — anchor on the unique declaration line:
228
281
  }
229
282
  ```
230
283
  </example>
284
+ </examples>
231
285
 
232
286
  <critical>
233
287
  - Edit payload: `{ path, edits[] }`. Each entry: `op`, `lines`, optional `pos`/`end`. No extra keys.
@@ -235,4 +289,6 @@ Good — anchor on the unique declaration line:
235
289
  - You **MUST** re-read the file after each edit call before issuing another on the same file. Tags shift after every edit, so reusing old tags produces mismatches.
236
290
  - You **MUST NOT** use this tool to reformat, reindent, or adjust whitespace — run the project's formatter instead. If the only difference is whitespace, it is formatting; leave it alone.
237
291
  - `lines` entries **MUST** be literal file content with indentation copied exactly from the `read` output. If the file uses tabs, use `\t` in JSON (a real tab character). Using `\\t` (backslash + t) writes the literal two-character string `\t` into the file.
292
+ - For `append`/`prepend`, `lines` **MUST NOT** repeat surrounding delimiters or existing sibling code. Insert only the new content.
293
+ - Before any range `replace`, you **MUST** check whether the replacement's last line duplicates the original line immediately after `end` (most often a closing `}`, `]`, or `)`). If it does, extend the range to consume that old boundary instead of leaving two closers behind.
238
294
  </critical>
package/src/sdk.ts CHANGED
@@ -1,5 +1,12 @@
1
- import { Agent, type AgentEvent, type AgentMessage, type AgentTool, INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
2
- import { type Message, type Model, supportsXhigh, type ThinkingLevel } from "@oh-my-pi/pi-ai";
1
+ import {
2
+ Agent,
3
+ type AgentEvent,
4
+ type AgentMessage,
5
+ type AgentTool,
6
+ INTENT_FIELD,
7
+ type ThinkingLevel,
8
+ } from "@oh-my-pi/pi-agent-core";
9
+ import type { Message, Model } from "@oh-my-pi/pi-ai";
3
10
 
4
11
  import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
5
12
  import type { Component } from "@oh-my-pi/pi-tui";
@@ -72,6 +79,7 @@ import {
72
79
  loadProjectContextFiles as loadContextFilesInternal,
73
80
  } from "./system-prompt";
74
81
  import { AgentOutputManager } from "./task/output-manager";
82
+ import { resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
75
83
  import {
76
84
  BashTool,
77
85
  BUILTIN_TOOLS,
@@ -117,10 +125,10 @@ export interface CreateAgentSessionOptions {
117
125
  /** Raw model pattern string (e.g. from --model CLI flag) to resolve after extensions load.
118
126
  * Used when model lookup is deferred because extension-provided models aren't registered yet. */
119
127
  modelPattern?: string;
120
- /** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
128
+ /** Thinking selector. Default: from settings, else unset */
121
129
  thinkingLevel?: ThinkingLevel;
122
130
  /** Models available for cycling (Ctrl+P in interactive mode) */
123
- scopedModels?: Array<{ model: Model; thinkingLevel: ThinkingLevel }>;
131
+ scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
124
132
 
125
133
  /** System prompt. String replaces default, function receives default and returns final. */
126
134
  systemPrompt?: string | ((defaultPrompt: string) => string);
@@ -456,12 +464,13 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
456
464
  runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
457
465
  );
458
466
  api.on("auto_compaction_start", async (event, ctx) =>
459
- runOnSession({ reason: "auto_compaction_start", trigger: event.reason }, ctx),
467
+ runOnSession({ reason: "auto_compaction_start", trigger: event.reason, action: event.action }, ctx),
460
468
  );
461
469
  api.on("auto_compaction_end", async (event, ctx) =>
462
470
  runOnSession(
463
471
  {
464
472
  reason: "auto_compaction_end",
473
+ action: event.action,
465
474
  result: event.result,
466
475
  aborted: event.aborted,
467
476
  willRetry: event.willRetry,
@@ -696,7 +705,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
696
705
 
697
706
  // If session has data and includes a thinking entry, restore it
698
707
  if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
699
- thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
708
+ thinkingLevel = existingSession.thinkingLevel as ThinkingLevel | undefined;
700
709
  }
701
710
 
702
711
  if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
@@ -705,14 +714,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
705
714
 
706
715
  // Fall back to settings default
707
716
  if (thinkingLevel === undefined) {
708
- thinkingLevel = settings.get("defaultThinkingLevel") ?? "off";
717
+ thinkingLevel = settings.get("defaultThinkingLevel");
709
718
  }
710
-
711
- // Clamp to model capabilities
712
- if (!model || !model.reasoning) {
713
- thinkingLevel = "off";
714
- } else if (thinkingLevel === "xhigh" && !supportsXhigh(model)) {
715
- thinkingLevel = "high";
719
+ if (model) {
720
+ thinkingLevel = resolveThinkingLevelForModel(model, thinkingLevel);
716
721
  }
717
722
 
718
723
  let skills: Skill[];
@@ -1343,12 +1348,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1343
1348
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "auto";
1344
1349
  const preferOpenAICodexWebsockets =
1345
1350
  openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
1351
+ const serviceTierSetting = settings.get("serviceTier");
1346
1352
 
1347
1353
  agent = new Agent({
1348
1354
  initialState: {
1349
1355
  systemPrompt,
1350
1356
  model,
1351
- thinkingLevel,
1357
+ thinkingLevel: toReasoningEffort(thinkingLevel),
1352
1358
  tools: initialTools,
1353
1359
  },
1354
1360
  convertToLlm: convertToLlmFinal,
@@ -1368,6 +1374,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1368
1374
  minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
1369
1375
  presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
1370
1376
  repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
1377
+ serviceTier: serviceTierSetting === "none" ? undefined : serviceTierSetting,
1371
1378
  kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
1372
1379
  preferWebsockets: preferOpenAICodexWebsockets,
1373
1380
  getToolContext: tc => toolContextStore.getContext(tc),
@@ -1418,6 +1425,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1418
1425
 
1419
1426
  session = new AgentSession({
1420
1427
  agent,
1428
+ thinkingLevel,
1421
1429
  sessionManager,
1422
1430
  settings,
1423
1431
  scopedModels: options.scopedModels,