@omercnet/paseo-omp 0.3.0-next.103.1 → 0.3.0-next.104.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "0.3.0-next.103.1",
3
+ "version": "0.3.0-next.104.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -48,6 +48,27 @@ const THINKING_OPTIONS: readonly ProviderThinkingOption[] = [
48
48
  { id: "xhigh", label: "XHigh", description: "Extra-high reasoning" },
49
49
  { id: "max", label: "Max", description: "Maximum reasoning" },
50
50
  ];
51
+ export const OMP_MAX_CATALOG_MODELS = 256;
52
+
53
+ export function selectOmpModels(
54
+ models: readonly OmpModel[],
55
+ activeModel: OmpModel | null | undefined,
56
+ ): OmpModel[] {
57
+ if (models.length <= OMP_MAX_CATALOG_MODELS) return [...models];
58
+ const selected = models.slice(0, OMP_MAX_CATALOG_MODELS);
59
+ if (!activeModel) return selected;
60
+ const active = models.find(
61
+ (model) => model.provider === activeModel.provider && model.id === activeModel.id,
62
+ );
63
+ if (
64
+ !active ||
65
+ selected.some((model) => model.provider === active.provider && model.id === active.id)
66
+ ) {
67
+ return selected;
68
+ }
69
+ selected[OMP_MAX_CATALOG_MODELS - 1] = active;
70
+ return selected;
71
+ }
51
72
 
52
73
  export function nativeOmpModelId(model: OmpModel): string {
53
74
  if (model.provider.includes("/")) {
@@ -60,14 +81,9 @@ export function ompModelId(model: OmpModel): string {
60
81
  const nativeIdentity = `${Buffer.byteLength(model.provider, "utf8")}:${model.provider}${Buffer.byteLength(model.id, "utf8")}:${model.id}`;
61
82
  return `omp:model:${createHash("sha256").update(nativeIdentity).digest("hex")}`;
62
83
  }
63
-
64
- export function mapOmpModels(
65
- models: readonly OmpModel[],
66
- serializer = new OmpPublicDataSerializer(),
67
- ): ProviderModel[] {
84
+ export function validateOmpModelIdentities(models: readonly OmpModel[]): void {
68
85
  const seenIds = new Map<string, string>();
69
- return models.map((model) => {
70
- const thinkingOptions = thinkingForModel(model);
86
+ for (const model of models) {
71
87
  const id = ompModelId(model);
72
88
  const nativeIdentity = nativeOmpModelId(model);
73
89
  const existing = seenIds.get(id);
@@ -76,6 +92,17 @@ export function mapOmpModels(
76
92
  }
77
93
  if (existing !== undefined) throw new Error("OMP reported a duplicate model identity");
78
94
  seenIds.set(id, nativeIdentity);
95
+ }
96
+ }
97
+
98
+ export function mapOmpModels(
99
+ models: readonly OmpModel[],
100
+ serializer = new OmpPublicDataSerializer(),
101
+ ): ProviderModel[] {
102
+ validateOmpModelIdentities(models);
103
+ return models.map((model) => {
104
+ const thinkingOptions = thinkingForModel(model);
105
+ const id = ompModelId(model);
79
106
  const provider = serializer.text(model.provider, 256);
80
107
  const modelId = serializer.text(model.id, 256);
81
108
  const name = model.name ? serializer.text(model.name, 256) : modelId;
@@ -143,14 +170,16 @@ export async function discoverOmpCatalog(
143
170
  ? [...configuredValues, ...(session.inheritedRedactionValues ?? [])]
144
171
  : configuredValues,
145
172
  );
146
- const models = mapOmpModels(nativeModels, serializer);
173
+ validateOmpModelIdentities(nativeModels);
174
+ const selectedNativeModels = selectOmpModels(nativeModels, state.model);
175
+ const models = mapOmpModels(selectedNativeModels, serializer);
147
176
  if (models.length === 0) throw new Error("OMP reported no available models");
148
177
  const defaultModel = state.model ? ompModelId(state.model) : models[0]?.id;
149
178
  const currentModel = state.model
150
- ? nativeModels.find(
179
+ ? selectedNativeModels.find(
151
180
  (model) => model.provider === state.model?.provider && model.id === state.model.id,
152
181
  )
153
- : nativeModels[0];
182
+ : selectedNativeModels[0];
154
183
  if (state.model && !currentModel) throw new Error("OMP reported an unadvertised active model");
155
184
  const thinkingOptions = thinkingForModel(currentModel);
156
185
  const defaultThinkingOption = thinkingOptions.some(
@@ -70,6 +70,7 @@ const MAX_OPTIONAL_METADATA_NODES = 4_096;
70
70
  const MAX_TASK_CORRELATION_BYTES = 256 * 1024;
71
71
  const MAX_TASK_CORRELATION_ITEMS = 1_024;
72
72
  const MAX_TASK_CORRELATION_NODES = 4_096;
73
+ const MAX_MODEL_CATALOG_ITEMS = 4_096;
73
74
  // Tool-intensive OMP turns legitimately exceed 64 blocks; transport byte/node budgets remain the
74
75
  // primary resource bounds.
75
76
  export const OMP_MAX_CONTENT_PARTS = 4_096;
@@ -115,6 +116,17 @@ function boundedString(maxBytes: number, minBytes = 0) {
115
116
  return bytes >= minBytes && bytes <= maxBytes;
116
117
  });
117
118
  }
119
+ function boundedOptionalStringArray(maxItems: number, maxBytes: number) {
120
+ return z.unknown().transform((value): string[] | undefined => {
121
+ if (!Array.isArray(value)) return undefined;
122
+ return value
123
+ .filter(
124
+ (item): item is string =>
125
+ typeof item === "string" && item.length > 0 && utf8Bytes(item) <= maxBytes,
126
+ )
127
+ .slice(0, maxItems);
128
+ });
129
+ }
118
130
 
119
131
  const IDENTIFIER = boundedString(MAX_ID_LENGTH, 1);
120
132
  const NAME = boundedString(MAX_NAME_LENGTH, 1);
@@ -191,6 +203,7 @@ function sanitizeLiveDisplayFrame(value: unknown): unknown {
191
203
  return changed ? { ...frame, messages } : value;
192
204
  }
193
205
  const OmpThinkingLevelSchema = z.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
206
+ const OmpCurrentThinkingLevelSchema = boundedString(32).optional().catch(undefined);
194
207
 
195
208
  function isBoundedJson(
196
209
  value: unknown,
@@ -546,19 +559,33 @@ const OmpAvailableCommandSchema = z.object({
546
559
  .optional(),
547
560
  source: boundedString(64).optional(),
548
561
  });
562
+ const OmpThinkingMetadataSchema = z
563
+ .unknown()
564
+ .transform((value): { efforts?: string[]; defaultLevel?: string } | undefined => {
565
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
566
+ const record = value as Record<string, unknown>;
567
+ const efforts = boundedOptionalStringArray(16, 32).parse(record.efforts);
568
+ const defaultLevel = boundedString(32).optional().catch(undefined).parse(record.defaultLevel);
569
+ return {
570
+ ...(efforts !== undefined ? { efforts } : {}),
571
+ ...(defaultLevel !== undefined ? { defaultLevel } : {}),
572
+ };
573
+ });
549
574
  const OmpModelSchema = z.object({
550
575
  provider: OMP_PROVIDER_NAME,
551
576
  id: NAME,
552
- name: boundedString(MAX_NAME_LENGTH).optional(),
553
- reasoning: z.boolean().optional(),
554
- thinking: z
555
- .object({
556
- efforts: z.array(boundedString(32)).max(16).optional(),
557
- defaultLevel: boundedString(32).optional(),
558
- })
559
- .optional(),
560
- input: z.array(NAME).max(16).optional(),
561
- contextWindow: z.number().int().nonnegative().max(100_000_000).nullable().optional(),
577
+ name: boundedString(MAX_NAME_LENGTH).optional().catch(undefined),
578
+ reasoning: z.boolean().optional().catch(undefined),
579
+ thinking: OmpThinkingMetadataSchema.optional(),
580
+ input: boundedOptionalStringArray(16, MAX_NAME_LENGTH).optional(),
581
+ contextWindow: z
582
+ .number()
583
+ .int()
584
+ .nonnegative()
585
+ .max(100_000_000)
586
+ .nullable()
587
+ .optional()
588
+ .catch(undefined),
562
589
  });
563
590
  const TokenCountSchema = z.number().int().nonnegative().max(MAX_TOKEN_COUNT);
564
591
  const OptionalTokenCountSchema = TokenCountSchema.nullable().optional();
@@ -610,13 +637,13 @@ const OmpCompactionResultSchema = z.object({
610
637
  });
611
638
  const OmpSessionStateSchema = z.object({
612
639
  model: OmpModelSchema.nullable().optional(),
613
- thinkingLevel: OmpThinkingLevelSchema.optional(),
640
+ thinkingLevel: OmpCurrentThinkingLevelSchema,
614
641
  isStreaming: z.boolean(),
615
642
  isCompacting: z.boolean(),
616
643
  sessionId: IDENTIFIER,
617
644
  autoCompactionEnabled: z.boolean().optional(),
618
- contextUsage: OmpContextUsageSchema.nullable().optional(),
619
- sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
645
+ contextUsage: OmpContextUsageSchema.nullable().optional().catch(undefined),
646
+ sessionFile: boundedString(MAX_PATH_LENGTH).optional().catch(undefined),
620
647
  });
621
648
  const OmpReadyFrameSchema = z.object({
622
649
  type: z.literal("ready"),
@@ -1217,7 +1244,7 @@ function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
1217
1244
  return 1_024;
1218
1245
  }
1219
1246
  const OmpModelsResultSchema = z.object({
1220
- models: z.array(OmpModelSchema).min(1).max(256),
1247
+ models: z.array(OmpModelSchema).min(1).max(MAX_MODEL_CATALOG_ITEMS),
1221
1248
  });
1222
1249
  const OmpPromptAckSchema = z.object({ agentInvoked: z.boolean().optional() }).optional();
1223
1250
  const OmpAvailableCommandsResultSchema = z.object({
@@ -2480,13 +2507,19 @@ class OmpRpcProcess {
2480
2507
  : response.data.data;
2481
2508
  const boundedFrame =
2482
2509
  responseData === response.data.data ? frame : { ...frame, data: responseData };
2483
- const responseItemLimit = isBranchHistory ? 1_024 : isHistory ? 100_000 : MAX_ARRAY_ITEMS;
2510
+ const responseItemLimit = isBranchHistory
2511
+ ? 1_024
2512
+ : isHistory
2513
+ ? 100_000
2514
+ : pending.command === "get_available_models"
2515
+ ? MAX_MODEL_CATALOG_ITEMS
2516
+ : MAX_ARRAY_ITEMS;
2484
2517
  const responseByteLimit =
2485
2518
  isBranchHistory || isHistory
2486
2519
  ? Math.min(MAX_REASSEMBLED_FRAME_BYTES, this.reassembledFrameLimit)
2487
2520
  : 2 * 1024 * 1024;
2488
- // A model catalog contains up to 256 structured models, so its aggregate
2489
- // node budget must exceed the small state/command-response budget.
2521
+ // Model catalogs are truncated before publication, while the transport still enforces
2522
+ // aggregate byte and node budgets over the complete response.
2490
2523
  const responseNodeLimit = isBranchHistory
2491
2524
  ? 4_096
2492
2525
  : isHistory
@@ -11,7 +11,15 @@ import type {
11
11
  ProviderUsage,
12
12
  } from "@getpaseo/plugin/server/provider";
13
13
  import { getForgeDefinitionOrNeutral } from "@getpaseo/protocol/forge-manifest";
14
- import { mapOmpModels, nativeOmpModelId, OMP_MODES, ompModelId, thinkingForModel } from "./catalog";
14
+ import {
15
+ mapOmpModels,
16
+ nativeOmpModelId,
17
+ OMP_MODES,
18
+ ompModelId,
19
+ selectOmpModels,
20
+ thinkingForModel,
21
+ validateOmpModelIdentities,
22
+ } from "./catalog";
15
23
  import {
16
24
  normalizeOmpSessionConfig,
17
25
  type OmpRecoveryOptions,
@@ -939,6 +947,8 @@ export class OmpProviderSession {
939
947
  private readonly pendingToolPermissions = new Map<string, PendingToolPermission>();
940
948
  private readonly inFlightToolPermissions = new Map<string, PendingToolPermission>();
941
949
  private readonly resolvedToolApprovalIds = new BoundedStringSet(MAX_TRACKED_ENTRY_IDS);
950
+ private unsupportedThinkingNoticePending = false;
951
+ private unsupportedThinkingNoticePublished = false;
942
952
 
943
953
  private constructor(
944
954
  id: string,
@@ -1057,10 +1067,9 @@ export class OmpProviderSession {
1057
1067
  }
1058
1068
  const startOptions: OmpStartOptions = {
1059
1069
  ...normalizedConfig,
1060
- // Model selection is authorized only after this runtime reports its exact catalog.
1061
- ...(resumeSessionId
1062
- ? { thinkingOption: undefined, systemPrompt: undefined, resumeSessionId }
1063
- : {}),
1070
+ // Thinking is authorized only after this runtime reports its exact model catalog.
1071
+ thinkingOption: undefined,
1072
+ ...(resumeSessionId ? { systemPrompt: undefined, resumeSessionId } : {}),
1064
1073
  signal,
1065
1074
  environment,
1066
1075
  };
@@ -1096,6 +1105,7 @@ export class OmpProviderSession {
1096
1105
  () => ({ available: false, commands: [] }),
1097
1106
  ),
1098
1107
  ]);
1108
+ validateOmpModelIdentities(nativeModels);
1099
1109
  if (effectiveConfig.persist && !native.canReplayHistory) {
1100
1110
  throw new OmpPublicError("OMP session persistence requires negotiated RPC protocol v2");
1101
1111
  }
@@ -1109,12 +1119,10 @@ export class OmpProviderSession {
1109
1119
  if (resumeSessionId && initialState.sessionId !== resumeSessionId) {
1110
1120
  throw new OmpPublicError("OMP resumed a different native session");
1111
1121
  }
1112
- const models = mapOmpModels(nativeModels, new OmpPublicDataSerializer(outputRedactionValues));
1113
- const nativeModelsByPublicId = new Map(
1114
- nativeModels.map((model) => [ompModelId(model), model] as const),
1115
- );
1116
1122
  if (!resumeSessionId && input.config.model) {
1117
- const selected = nativeModelsByPublicId.get(input.config.model);
1123
+ const selected = selectOmpModels(nativeModels, state.model).find(
1124
+ (model) => ompModelId(model) === input.config.model,
1125
+ );
1118
1126
  if (!selected) {
1119
1127
  throw new OmpPublicError("OMP model is not advertised by the configured session runtime");
1120
1128
  }
@@ -1123,9 +1131,29 @@ export class OmpProviderSession {
1123
1131
  state = await native.getState();
1124
1132
  }
1125
1133
  }
1134
+ let currentModel = state.model
1135
+ ? nativeModels.find(
1136
+ (model) => model.provider === state.model?.provider && model.id === state.model.id,
1137
+ )
1138
+ : undefined;
1139
+ if (state.model && !currentModel) {
1140
+ throw new OmpPublicError("OMP runtime selected an unadvertised model");
1141
+ }
1142
+ if (!resumeSessionId && input.config.thinkingOption !== undefined) {
1143
+ if (
1144
+ !thinkingForModel(currentModel).some(
1145
+ (option) => option.id === input.config.thinkingOption,
1146
+ )
1147
+ ) {
1148
+ throw new OmpPublicError("OMP thinking level is unavailable for the selected model");
1149
+ }
1150
+ if (state.thinkingLevel !== input.config.thinkingOption) {
1151
+ await native.setThinkingLevel(input.config.thinkingOption);
1152
+ }
1153
+ }
1126
1154
  const reconciledConfigRevision = bootstrapConfigRevision;
1127
1155
  state = await native.getState();
1128
- const currentModel = state.model
1156
+ currentModel = state.model
1129
1157
  ? nativeModels.find(
1130
1158
  (model) => model.provider === state.model?.provider && model.id === state.model.id,
1131
1159
  )
@@ -1133,21 +1161,21 @@ export class OmpProviderSession {
1133
1161
  if (state.model && !currentModel) {
1134
1162
  throw new OmpPublicError("OMP runtime selected an unadvertised model");
1135
1163
  }
1164
+ const selectedNativeModels = selectOmpModels(nativeModels, state.model);
1165
+ const models = mapOmpModels(
1166
+ selectedNativeModels,
1167
+ new OmpPublicDataSerializer(outputRedactionValues),
1168
+ );
1169
+ const nativeModelsByPublicId = new Map(
1170
+ nativeModels.map((model) => [ompModelId(model), model] as const),
1171
+ );
1136
1172
  const thinkingOptions = thinkingForModel(currentModel);
1137
- const committedThinkingLevel = applicableThinkingLevel(currentModel, state.thinkingLevel);
1138
- if (
1139
- !resumeSessionId &&
1140
- input.config.thinkingOption !== undefined &&
1141
- !thinkingOptions.some((option) => option.id === input.config.thinkingOption)
1142
- ) {
1143
- throw new OmpPublicError("OMP thinking level is unavailable for the selected model");
1144
- }
1145
- if (
1146
- committedThinkingLevel &&
1147
- !thinkingOptions.some((option) => option.id === committedThinkingLevel)
1148
- ) {
1149
- throw new OmpPublicError("OMP runtime selected an unsupported thinking level");
1150
- }
1173
+ const applicableLevel = applicableThinkingLevel(currentModel, state.thinkingLevel);
1174
+ const committedThinkingLevel = thinkingOptions.some((option) => option.id === applicableLevel)
1175
+ ? applicableLevel
1176
+ : undefined;
1177
+ const unsupportedThinkingLevel =
1178
+ applicableLevel !== undefined && committedThinkingLevel === undefined;
1151
1179
  const configState: ProviderConfigState = {
1152
1180
  ...(state.model ? { model: ompModelId(state.model) } : {}),
1153
1181
  mode: normalizedConfig.mode ?? "full",
@@ -1210,6 +1238,7 @@ export class OmpProviderSession {
1210
1238
  retireRewindSession,
1211
1239
  scheduler,
1212
1240
  );
1241
+ session.unsupportedThinkingNoticePending = unsupportedThinkingLevel;
1213
1242
 
1214
1243
  if (bootstrapConfigRevision !== reconciledConfigRevision) {
1215
1244
  session.configRefreshDirty = true;
@@ -1268,6 +1297,7 @@ export class OmpProviderSession {
1268
1297
  : {}),
1269
1298
  ...(this.config.title ? { title: this.dataFilter.text(this.config.title, 256) } : {}),
1270
1299
  });
1300
+ this.publishUnsupportedThinkingNotice();
1271
1301
  this.emit({ type: "session.config", sessionId: this.id, config: this.configState });
1272
1302
  if (this.replayHistoryOnOpen) await this.replayHistory(true);
1273
1303
  this.publishCommands(this.commandCatalog);
@@ -1275,6 +1305,22 @@ export class OmpProviderSession {
1275
1305
  this.readyPublished = true;
1276
1306
  if (this.configRefreshDirty) this.scheduleCommittedConfigRefresh();
1277
1307
  }
1308
+ private publishUnsupportedThinkingNotice(): void {
1309
+ if (!this.unsupportedThinkingNoticePending || this.unsupportedThinkingNoticePublished) return;
1310
+ this.unsupportedThinkingNoticePending = false;
1311
+ this.unsupportedThinkingNoticePublished = true;
1312
+ this.emit({
1313
+ type: "session.notice",
1314
+ sessionId: this.id,
1315
+ notice: {
1316
+ id: "omp:unsupported-thinking-level",
1317
+ severity: "warning",
1318
+ title: "OMP thinking level unavailable",
1319
+ description:
1320
+ "OMP reported a thinking level that the active model does not advertise. Paseo omitted it from the session configuration.",
1321
+ },
1322
+ });
1323
+ }
1278
1324
 
1279
1325
  private usageFrom(
1280
1326
  state: OmpSessionState | undefined,
@@ -1685,6 +1731,17 @@ export class OmpProviderSession {
1685
1731
  if (this.activeTurn || beforeState.isStreaming || beforeState.isCompacting) {
1686
1732
  throw new OmpPublicError("Cannot rewind the OMP conversation while a turn is active");
1687
1733
  }
1734
+ const beforeAdvertisedModel = beforeState.model
1735
+ ? this.nativeModelsByPublicId.get(ompModelId(beforeState.model))
1736
+ : undefined;
1737
+ if (beforeState.model && !beforeAdvertisedModel) {
1738
+ throw new OmpPublicError("OMP runtime selected an unadvertised model");
1739
+ }
1740
+ const restorableThinkingLevel = thinkingForModel(beforeAdvertisedModel).some(
1741
+ (option) => option.id === beforeState.thinkingLevel,
1742
+ )
1743
+ ? beforeState.thinkingLevel
1744
+ : undefined;
1688
1745
  branchMutationPossible = true;
1689
1746
  let result: OmpBranchResult;
1690
1747
  try {
@@ -1727,13 +1784,10 @@ export class OmpProviderSession {
1727
1784
  }
1728
1785
  await runtime.setModel(beforeState.model.provider, beforeState.model.id);
1729
1786
  }
1730
- if (thinkingChanged) {
1731
- if (!beforeState.thinkingLevel) {
1732
- throw new OmpPublicError("OMP changed thinking level while rewinding the conversation");
1733
- }
1734
- await runtime.setThinkingLevel(beforeState.thinkingLevel);
1787
+ if (thinkingChanged && restorableThinkingLevel) {
1788
+ await runtime.setThinkingLevel(restorableThinkingLevel);
1735
1789
  }
1736
- if (modelChanged || thinkingChanged) {
1790
+ if (modelChanged || (thinkingChanged && restorableThinkingLevel)) {
1737
1791
  state = await runtime.getState();
1738
1792
  this.requireCurrentRuntime(runtime, generation);
1739
1793
  }
@@ -1741,7 +1795,7 @@ export class OmpProviderSession {
1741
1795
  state.sessionId !== this.nativeSessionId ||
1742
1796
  state.model?.provider !== beforeState.model?.provider ||
1743
1797
  state.model?.id !== beforeState.model?.id ||
1744
- state.thinkingLevel !== beforeState.thinkingLevel
1798
+ (restorableThinkingLevel !== undefined && state.thinkingLevel !== restorableThinkingLevel)
1745
1799
  ) {
1746
1800
  throw new OmpPublicError("OMP did not preserve session configuration while rewinding");
1747
1801
  }
@@ -2341,7 +2395,10 @@ export class OmpProviderSession {
2341
2395
  const targetModel = targetModelId
2342
2396
  ? this.nativeModelsByPublicId.get(targetModelId)
2343
2397
  : undefined;
2344
- if (input.changes.model !== undefined && !targetModel) {
2398
+ const targetModelPublished = this.configState.models.some(
2399
+ (model) => model.id === targetModelId,
2400
+ );
2401
+ if (input.changes.model !== undefined && (!targetModel || !targetModelPublished)) {
2345
2402
  throw new OmpPublicError("OMP model selection is unavailable");
2346
2403
  }
2347
2404
  if (
@@ -2588,13 +2645,18 @@ export class OmpProviderSession {
2588
2645
  throw new OmpCatalogEscape("OMP runtime selected an unadvertised model");
2589
2646
  }
2590
2647
  const thinkingOptions = thinkingForModel(advertisedModel);
2591
- const committedThinkingLevel = applicableThinkingLevel(advertisedModel, state.thinkingLevel);
2592
- if (
2593
- committedThinkingLevel &&
2594
- !thinkingOptions.some((option) => option.id === committedThinkingLevel)
2595
- ) {
2596
- throw new OmpCatalogEscape("OMP runtime selected an unsupported thinking level");
2648
+ const applicableLevel = applicableThinkingLevel(advertisedModel, state.thinkingLevel);
2649
+ const committedThinkingLevel = thinkingOptions.some((option) => option.id === applicableLevel)
2650
+ ? applicableLevel
2651
+ : undefined;
2652
+ if (applicableLevel !== undefined && committedThinkingLevel === undefined) {
2653
+ this.unsupportedThinkingNoticePending = true;
2654
+ this.publishUnsupportedThinkingNotice();
2597
2655
  }
2656
+ const models = mapOmpModels(
2657
+ selectOmpModels([...this.nativeModelsByPublicId.values()], state.model),
2658
+ this.dataFilter,
2659
+ );
2598
2660
  this.configRefreshAttempts = 0;
2599
2661
  this.cancelConfigRefreshRetry();
2600
2662
  const nextConfig: ProviderConfigState = {
@@ -2603,12 +2665,15 @@ export class OmpProviderSession {
2603
2665
  ...(committedThinkingLevel
2604
2666
  ? { thinkingOption: committedThinkingLevel }
2605
2667
  : { thinkingOption: undefined }),
2668
+ models,
2606
2669
  thinkingOptions,
2607
2670
  };
2608
2671
  const changed =
2609
2672
  force ||
2610
2673
  nextConfig.model !== this.configState.model ||
2611
2674
  nextConfig.thinkingOption !== this.configState.thinkingOption ||
2675
+ nextConfig.models.some((model, index) => model.id !== this.configState.models[index]?.id) ||
2676
+ this.configState.models.length !== nextConfig.models.length ||
2612
2677
  nextConfig.thinkingOptions.length !== this.configState.thinkingOptions.length ||
2613
2678
  nextConfig.thinkingOptions.some(
2614
2679
  (option, index) =>
@@ -2929,13 +2994,6 @@ export class OmpProviderSession {
2929
2994
  if (state.model && !advertisedModel) {
2930
2995
  throw new Error("OMP recovered with an unadvertised model");
2931
2996
  }
2932
- const recoveredThinkingLevel = applicableThinkingLevel(advertisedModel, state.thinkingLevel);
2933
- if (
2934
- recoveredThinkingLevel &&
2935
- !thinkingForModel(advertisedModel).some((option) => option.id === recoveredThinkingLevel)
2936
- ) {
2937
- throw new Error("OMP recovered with an unsupported thinking level");
2938
- }
2939
2997
  if (this.closed) throw new Error("OMP session closed while runtime recovery was pending");
2940
2998
  if (!this.hostTools.isBoundTo(recovered)) {
2941
2999
  throw new Error("OMP host tool bridge detached during recovery");