mixdog 1.3.4 → 1.3.5

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 (197) hide show
  1. package/LICENSES/oh-my-pi-MIT.txt +23 -0
  2. package/NOTICE.md +15 -0
  3. package/README.md +11 -0
  4. package/package.json +2 -1
  5. package/scripts/embed-google-oauth-client.mjs +49 -0
  6. package/scripts/lib/test-spawn-ledger.mjs +46 -1
  7. package/scripts/test.mjs +5 -0
  8. package/src/defaults/shipped-history.json +8 -0
  9. package/src/defaults/skills/browser-use/SKILL.md +21 -9
  10. package/src/defaults/skills/setup/SKILL.md +7 -2
  11. package/src/defaults/skills/setup/references/actions.md +82 -27
  12. package/src/defaults/skills/setup/references/surfaces.md +67 -34
  13. package/src/defaults/skills/setup-guide/SKILL.md +3 -3
  14. package/src/lib/keychain-cjs.cjs +359 -29
  15. package/src/rules/agent-mode/AGENT_MODE.md +10 -2
  16. package/src/rules/agent-mode/MIXY.md +12 -0
  17. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +18 -0
  19. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
  20. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  21. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
  22. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
  23. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
  24. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
  25. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
  26. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
  27. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
  35. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
  38. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
  41. package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
  42. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
  43. package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
  45. package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
  46. package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
  47. package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
  48. package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
  49. package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
  50. package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
  51. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
  52. package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
  53. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
  55. package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
  58. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
  60. package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
  61. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
  64. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
  65. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
  69. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
  71. package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
  72. package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
  78. package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
  83. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
  84. package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
  86. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
  87. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
  88. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
  90. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
  91. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  92. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
  93. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
  97. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
  99. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
  102. package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
  103. package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
  104. package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
  105. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
  106. package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
  107. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
  109. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
  110. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
  111. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
  112. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
  113. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
  114. package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
  115. package/src/runtime/channels/lib/scheduler.mjs +109 -14
  116. package/src/runtime/channels/lib/schedules-db.mjs +49 -0
  117. package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
  118. package/src/runtime/channels/lib/worker-main.mjs +9 -0
  119. package/src/runtime/office/com/com-adapter.mjs +17 -1
  120. package/src/runtime/office/com/office-com-host.ps1 +51 -1
  121. package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
  122. package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
  123. package/src/runtime/office/pdf/document-preview.mjs +326 -8
  124. package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
  125. package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
  126. package/src/runtime/office/portable/portable-soffice.mjs +45 -5
  127. package/src/runtime/shared/bot-hops.mjs +62 -2
  128. package/src/runtime/shared/config.mjs +49 -22
  129. package/src/runtime/shared/connector-catalog.d.mts +4 -2
  130. package/src/runtime/shared/connector-catalog.mjs +16 -8
  131. package/src/runtime/shared/developer-options.mjs +32 -1
  132. package/src/runtime/shared/google-oauth-client.generated.json +4 -0
  133. package/src/runtime/shared/google-oauth-client.mjs +34 -9
  134. package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
  135. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
  136. package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
  137. package/src/runtime/shared/llm/well-formed.mjs +66 -0
  138. package/src/runtime/shared/provider-accounts.mjs +224 -58
  139. package/src/runtime/shared/provider-api-key.mjs +19 -8
  140. package/src/runtime/shared/routine-trigger.mjs +42 -0
  141. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  142. package/src/runtime/shared/transcript-metadata.mjs +38 -0
  143. package/src/runtime/shared/user-data-guard.mjs +30 -2
  144. package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
  145. package/src/session-runtime/agent-home-delivery.mjs +57 -13
  146. package/src/session-runtime/agent-room-delivery.mjs +28 -1
  147. package/src/session-runtime/agent-task-sessions.mjs +292 -79
  148. package/src/session-runtime/boot/begin.mjs +3 -2
  149. package/src/session-runtime/boot/tools.mjs +2 -0
  150. package/src/session-runtime/bot-chain.mjs +32 -0
  151. package/src/session-runtime/bot-status.mjs +7 -4
  152. package/src/session-runtime/channel-config-api.mjs +63 -17
  153. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
  154. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
  155. package/src/session-runtime/internal-tool-executor.mjs +4 -0
  156. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
  157. package/src/session-runtime/mixy-heartbeat.mjs +807 -0
  158. package/src/session-runtime/model-route/route-tuning.mjs +10 -6
  159. package/src/session-runtime/owned-automation-run.mjs +33 -12
  160. package/src/session-runtime/provider-auth/login.mjs +7 -0
  161. package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
  162. package/src/session-runtime/routine-home-turn.mjs +33 -6
  163. package/src/session-runtime/schedule-session-run.mjs +7 -3
  164. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
  165. package/src/session-runtime/services/channel-admin.mjs +66 -18
  166. package/src/session-runtime/services/channel-worker.mjs +1 -1
  167. package/src/session-runtime/services/provider-admin.mjs +128 -25
  168. package/src/session-runtime/services/schedule-access.mjs +14 -0
  169. package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
  170. package/src/session-runtime/session-create/session-options.mjs +7 -4
  171. package/src/session-runtime/settings-system-api.mjs +16 -1
  172. package/src/session-runtime/setup-tool/executor.mjs +49 -14
  173. package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
  174. package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
  175. package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
  176. package/src/session-runtime/task-session-route.mjs +99 -0
  177. package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
  178. package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
  179. package/src/session-runtime/tool-surface.mjs +4 -0
  180. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
  181. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
  182. package/src/session-runtime/workspace/transitions.mjs +10 -2
  183. package/src/standalone/daemon-session-control.mjs +88 -20
  184. package/src/standalone/daemon.mjs +8 -0
  185. package/src/standalone/session-protocol.mjs +5 -0
  186. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
  187. package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
  188. package/src/standalone/session-service.mjs +13 -0
  189. package/src/standalone/session-tool-output.mjs +8 -5
  190. package/src/tui/app/model-options.mjs +2 -0
  191. package/src/tui/dist/index.mjs +24 -2
  192. package/src/tui/session/notice-surface.mjs +17 -1
  193. package/src/tui/session/session-api/integrations.mjs +1 -0
  194. package/src/tui/session/session-api/lifecycle.mjs +8 -0
  195. package/src/tui/session/session-api/settings.mjs +14 -2
  196. package/src/tui/session/turn-stream.mjs +17 -1
  197. package/src/tui/session/turn.mjs +22 -2
@@ -0,0 +1,342 @@
1
+ // Model-class identity for Devin wire decisions: which backend family a model
2
+ // id names (Gemini tool-schema rules) and the thinking form another model's
3
+ // reasoning is demoted into when it replays as plain text.
4
+ //
5
+ // Class matchers rank exact(4) > bounded(3) > namespace(2) > prefix(1) >
6
+ // glob(0), token length breaking ties; an equal rank across two classes
7
+ // resolves to 'unknown'. A trailing effort/thinking suffix is collapsed off
8
+ // the id before matching.
9
+
10
+ const MATCHER_RANK = Object.freeze({ exact: 4, bounded: 3, namespace: 2, prefix: 1, glob: 0 });
11
+
12
+ // [class, [[kind, token, boundedNamespace?], ...]]
13
+ const CLASS_MATCHERS = Object.freeze([
14
+ [
15
+ 'ai21',
16
+ [
17
+ ['bounded', 'jamba'],
18
+ ['namespace', 'ai21'],
19
+ ],
20
+ ],
21
+ [
22
+ 'amazon',
23
+ [
24
+ ['bounded', 'nova'],
25
+ ['bounded', 'titan'],
26
+ ['namespace', 'amazon', true],
27
+ ],
28
+ ],
29
+ [
30
+ 'anthropic',
31
+ [
32
+ ['namespace', 'anthropic', true],
33
+ ['bounded', 'anthropic'],
34
+ ['bounded', 'claude'],
35
+ ],
36
+ ],
37
+ ['baidu', [['bounded', 'ernie']]],
38
+ ['bytedance', [['bounded', 'doubao']]],
39
+ [
40
+ 'cohere',
41
+ [
42
+ ['bounded', 'command'],
43
+ ['namespace', 'cohere'],
44
+ ],
45
+ ],
46
+ [
47
+ 'deepseek',
48
+ [
49
+ ['namespace', 'deepseek', true],
50
+ ['bounded', 'deepseek'],
51
+ ],
52
+ ],
53
+ ['gemini', [['bounded', 'gemini']]],
54
+ ['gemma', [['bounded', 'gemma']]],
55
+ [
56
+ 'glm',
57
+ [
58
+ ['bounded', 'glm'],
59
+ ['bounded', 'zai-glm'],
60
+ ],
61
+ ],
62
+ [
63
+ 'gpt-oss',
64
+ [
65
+ ['namespace', 'gpt-oss', true],
66
+ ['bounded', 'gpt-oss'],
67
+ ],
68
+ ],
69
+ [
70
+ 'kimi',
71
+ [
72
+ ['namespace', 'moonshotai'],
73
+ ['bounded', 'kimi'],
74
+ ['exact', 'k3'],
75
+ ['exact', 'k3-256k'],
76
+ ],
77
+ ],
78
+ [
79
+ 'meta',
80
+ [
81
+ ['bounded', 'muse-spark'],
82
+ ['bounded', 'muse-wonder'],
83
+ ['bounded', 'llama'],
84
+ ['bounded', 'meta-llama'],
85
+ ['namespace', 'meta-llama'],
86
+ ],
87
+ ],
88
+ ['mimo', [['bounded', 'mimo']]],
89
+ [
90
+ 'minimax',
91
+ [
92
+ ['namespace', 'minimax'],
93
+ ['bounded', 'minimax'],
94
+ ['bounded', 'hailuo'],
95
+ ],
96
+ ],
97
+ [
98
+ 'mistral',
99
+ [
100
+ ['bounded', 'mistral'],
101
+ ['bounded', 'mixtral'],
102
+ ],
103
+ ],
104
+ [
105
+ 'openai',
106
+ [
107
+ ['namespace', 'openai'],
108
+ ['namespace', 'openai', true],
109
+ ['prefix', 'gpt-'],
110
+ ['prefix', 'chatgpt-'],
111
+ ['prefix', 'codex-'],
112
+ ['exact', 'o1'],
113
+ ['prefix', 'o1-'],
114
+ ['prefix', 'o1.'],
115
+ ['exact', 'o3'],
116
+ ['prefix', 'o3-'],
117
+ ['prefix', 'o3.'],
118
+ ['exact', 'o4'],
119
+ ['prefix', 'o4-'],
120
+ ['prefix', 'o4.'],
121
+ ],
122
+ ],
123
+ [
124
+ 'qwen',
125
+ [
126
+ ['bounded', 'qwen'],
127
+ ['bounded', 'deepseek-r1-distill-qwen'],
128
+ ['glob', '*distill-qwen*'],
129
+ ],
130
+ ],
131
+ ['stepfun', [['bounded', 'step']]],
132
+ [
133
+ 'xai',
134
+ [
135
+ ['prefix', 'cursor-grok-'],
136
+ ['bounded', 'grok'],
137
+ ['namespace', 'x-ai'],
138
+ ['namespace', 'xai', true],
139
+ ],
140
+ ],
141
+ ]);
142
+
143
+ // Reviewed provider-agnostic identity overrides (exact bare id or glob).
144
+ const EXACT_OVERRIDES = Object.freeze(
145
+ new Map([
146
+ ['daybreak-blue-latest', 'unknown'],
147
+ ['daybreak-red-latest', 'unknown'],
148
+ ['gpt-daybreak-blue-latest', 'openai'],
149
+ ['gpt-daybreak-red-latest', 'openai'],
150
+ ])
151
+ );
152
+ const GLOB_OVERRIDES = Object.freeze([
153
+ ['*bonsai-27b*', 'qwen'],
154
+ ['*bonsai-2-27b*', 'qwen'],
155
+ ]);
156
+
157
+ // Trailing variant suffixes collapsed off an id before class matching.
158
+ const COLLAPSE_SUFFIXES = Object.freeze([
159
+ ['-thinking'],
160
+ ['-extra-high'],
161
+ ['-none'],
162
+ ['-minimal'],
163
+ ['-medium'],
164
+ ['-xhigh'],
165
+ ['-high'],
166
+ ['-low'],
167
+ ['-max', 'qwen'],
168
+ ]);
169
+
170
+ function bareOf(id) {
171
+ const slash = id.lastIndexOf('/');
172
+ return slash === -1 ? id : id.slice(slash + 1);
173
+ }
174
+
175
+ function globMatch(pattern, value) {
176
+ const segments = pattern.split('*');
177
+ if (segments.length === 1) return value === pattern;
178
+ const head = segments[0];
179
+ if (!value.startsWith(head)) return false;
180
+ let remainder = value.slice(head.length);
181
+ for (let i = 1; i < segments.length - 1; i += 1) {
182
+ const segment = segments[i];
183
+ if (!segment) continue;
184
+ const found = remainder.indexOf(segment);
185
+ if (found === -1) return false;
186
+ remainder = remainder.slice(found + segment.length);
187
+ }
188
+ const last = segments.at(-1);
189
+ return last === '' || remainder.endsWith(last);
190
+ }
191
+
192
+ // `token` itself, or `token` followed by - _ . : or a digit.
193
+ function boundedMatch(value, token) {
194
+ if (value === token) return true;
195
+ if (!value.startsWith(token)) return false;
196
+ const next = value.charCodeAt(token.length);
197
+ return next === 45 || next === 95 || next === 46 || next === 58 || (next >= 48 && next <= 57);
198
+ }
199
+
200
+ function matcherMatches([kind, token, boundedNamespace], lower, bare) {
201
+ switch (kind) {
202
+ case 'exact':
203
+ return bare === token;
204
+ case 'bounded':
205
+ return boundedMatch(bare, token);
206
+ case 'namespace':
207
+ return (boundedNamespace ? lower.split(/[/.:]/) : lower.split('/')).some(
208
+ (part) => part && (boundedNamespace ? boundedMatch(part, token) : part === token)
209
+ );
210
+ case 'prefix':
211
+ return bare.startsWith(token);
212
+ default:
213
+ return globMatch(token, bare);
214
+ }
215
+ }
216
+
217
+ function classOfLogicalId(model) {
218
+ const lower = model.trim().toLowerCase();
219
+ const bare = bareOf(lower);
220
+ let winner = null;
221
+ let tied = false;
222
+ for (const [cls, matchers] of CLASS_MATCHERS) {
223
+ for (const matcher of matchers) {
224
+ if (!matcherMatches(matcher, lower, bare)) continue;
225
+ const rank = [MATCHER_RANK[matcher[0]], matcher[1].length];
226
+ if (winner && winner.rank[0] === rank[0] && winner.rank[1] === rank[1] && winner.cls !== cls) {
227
+ tied = true;
228
+ } else if (!winner || winner.rank[0] < rank[0] || (winner.rank[0] === rank[0] && winner.rank[1] < rank[1])) {
229
+ winner = { rank, cls };
230
+ tied = false;
231
+ }
232
+ }
233
+ }
234
+ return tied || !winner ? 'unknown' : winner.cls;
235
+ }
236
+
237
+ function collapsedId(model) {
238
+ const lower = model.toLowerCase();
239
+ const bare = bareOf(lower);
240
+ let winner = null;
241
+ for (const [suffix, exceptBarePrefix] of COLLAPSE_SUFFIXES) {
242
+ if (!lower.endsWith(suffix)) continue;
243
+ if (exceptBarePrefix && bare.startsWith(exceptBarePrefix)) continue;
244
+ if (!winner || suffix.length > winner.length) winner = suffix;
245
+ }
246
+ return winner ? model.slice(0, model.length - winner.length) : model;
247
+ }
248
+
249
+ /** The model class a wire or catalog id names ('gemini', 'anthropic', 'openai', …, or 'unknown'). */
250
+ export function devinModelClass(modelId) {
251
+ const raw = String(modelId || '');
252
+ const trimmed = raw.trim();
253
+ const bare = bareOf(trimmed).toLowerCase();
254
+ const exact = EXACT_OVERRIDES.get(bare);
255
+ if (exact) return exact;
256
+ const glob = GLOB_OVERRIDES.filter(([pattern]) => globMatch(pattern, bare)).sort(
257
+ (a, b) => b[0].replaceAll('*', '').length - a[0].replaceAll('*', '').length
258
+ );
259
+ if (glob.length && !(glob[1] && glob[1][0].replaceAll('*', '').length === glob[0][0].replaceAll('*', '').length)) {
260
+ return glob[0][1];
261
+ }
262
+ return classOfLogicalId(trimmed.length === raw.length ? collapsedId(trimmed) : trimmed);
263
+ }
264
+
265
+ /** Whether the catalog id or the concrete wire uid runs on a Gemini backend. */
266
+ export function isDevinGeminiModel(...ids) {
267
+ return ids.some(
268
+ (id) => typeof id === 'string' && (devinModelClass(id) === 'gemini' || id.startsWith('MODEL_GOOGLE_GEMINI_'))
269
+ );
270
+ }
271
+
272
+ const isAsciiWhitespace = (code) =>
273
+ code === 9 || code === 10 || code === 11 || code === 12 || code === 13 || code === 32;
274
+
275
+ function trimAsciiStart(text, start, end) {
276
+ let cursor = start;
277
+ while (cursor < end && isAsciiWhitespace(text.charCodeAt(cursor))) cursor += 1;
278
+ return cursor;
279
+ }
280
+
281
+ function trimAsciiEnd(text, start, end) {
282
+ let cursor = end;
283
+ while (cursor > start && isAsciiWhitespace(text.charCodeAt(cursor - 1))) cursor -= 1;
284
+ return cursor;
285
+ }
286
+
287
+ function delimitedClose(open, close, text, start, end) {
288
+ let depth = 1;
289
+ let cursor = start;
290
+ while (cursor < end) {
291
+ const nextClose = text.indexOf(close, cursor);
292
+ if (nextClose < 0 || nextClose >= end) return -1;
293
+ const nextOpen = text.indexOf(open, cursor);
294
+ if (nextOpen >= 0 && nextOpen < nextClose) {
295
+ depth += 1;
296
+ cursor = nextOpen + open.length;
297
+ continue;
298
+ }
299
+ depth -= 1;
300
+ if (depth === 0) return nextClose;
301
+ cursor = nextClose + close.length;
302
+ }
303
+ return -1;
304
+ }
305
+
306
+ // Text already wrapped in `open`…`close` blocks is unwrapped so it is not
307
+ // wrapped twice.
308
+ function unwrapDelimited(open, close, text) {
309
+ const end = trimAsciiEnd(text, 0, text.length);
310
+ let cursor = trimAsciiStart(text, 0, end);
311
+ if (cursor >= end || !text.startsWith(open, cursor)) return text;
312
+ const segments = [];
313
+ while (cursor < end) {
314
+ if (!text.startsWith(open, cursor)) return text;
315
+ const innerStart = cursor + open.length;
316
+ const innerEnd = delimitedClose(open, close, text, innerStart, end);
317
+ if (innerEnd < 0) return text;
318
+ const trimmedEnd = trimAsciiEnd(text, innerStart, innerEnd);
319
+ const trimmedStart = trimAsciiStart(text, innerStart, trimmedEnd);
320
+ segments.push(unwrapDelimited(open, close, text.slice(trimmedStart, trimmedEnd)));
321
+ cursor = trimAsciiStart(text, innerEnd + close.length, end);
322
+ }
323
+ return segments.join('\n');
324
+ }
325
+
326
+ const THINK_BLOCK_CLASSES = new Set(['glm', 'kimi', 'qwen', 'deepseek', 'openai', 'gpt-oss', 'gemma']);
327
+
328
+ /**
329
+ * Another model's reasoning as plain text in the target model's own thinking
330
+ * form: bare prose for Claude (tagged reasoning is refused there), a fenced
331
+ * ```thinking block for Gemini, <think> for the <think>-dialect families, and
332
+ * <thinking> otherwise. No trailing delimiter.
333
+ */
334
+ export function devinDemotedThinking(targetModelId, text) {
335
+ if (!text) return '';
336
+ const value = text.toWellFormed();
337
+ const cls = devinModelClass(targetModelId);
338
+ if (cls === 'anthropic') return value;
339
+ if (cls === 'gemini') return `\`\`\`thinking\n${value}\n\`\`\``;
340
+ if (THINK_BLOCK_CLASSES.has(cls)) return `<think>\n${value}\n</think>`;
341
+ return `<thinking>\n${unwrapDelimited('<thinking>', '</thinking>', value)}\n</thinking>`;
342
+ }