@opengeni/runtime 3.3.0 → 3.4.0

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 (64) hide show
  1. package/dist/agent-LJT5NPWT.js +9 -0
  2. package/dist/assets/codemode-client.json +1 -1
  3. package/dist/browser-current-tab.d.ts +46 -0
  4. package/dist/browser-model-text.d.ts +126 -0
  5. package/dist/{chunk-XRX57ZQO.js → chunk-3DFH3ZHU.js} +784 -121
  6. package/dist/chunk-3DFH3ZHU.js.map +1 -0
  7. package/dist/{chunk-HJSIPTFA.js → chunk-4XDJ4TJA.js} +2 -2
  8. package/dist/{chunk-XQ23VCZ4.js → chunk-6FQ6KEII.js} +3 -3
  9. package/dist/{chunk-7LJ7KDPI.js → chunk-ERD4ICVB.js} +4 -3
  10. package/dist/{chunk-EK7IW2FR.js → chunk-ETTCGPBT.js} +224 -6
  11. package/dist/chunk-ETTCGPBT.js.map +1 -0
  12. package/dist/{chunk-7OUG7WBV.js → chunk-GWRBALGQ.js} +181 -13
  13. package/dist/chunk-GWRBALGQ.js.map +1 -0
  14. package/dist/{chunk-YG26XE7B.js → chunk-JYDG2A7H.js} +2 -2
  15. package/dist/{chunk-RSHQSLEJ.js → chunk-KIL4FGX5.js} +3 -3
  16. package/dist/{chunk-NWS4HPB7.js → chunk-MIAL5I4O.js} +2 -2
  17. package/dist/{chunk-Z5U6WE22.js → chunk-PRZPGRIK.js} +2 -2
  18. package/dist/{chunk-KCPW4K7L.js → chunk-PZQUUBEB.js} +2 -2
  19. package/dist/{chunk-5WJOPANS.js → chunk-Y5THZH5N.js} +7 -7
  20. package/dist/codex-tool-search.d.ts +9 -0
  21. package/dist/gmail-rest-mcp.js +8 -8
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.js +14 -12
  24. package/dist/interaction-tool-json-schema.d.ts +27 -0
  25. package/dist/{lazy-manifest-AJ6XPJ4F.js → lazy-manifest-BLZ3UZLX.js} +4 -4
  26. package/dist/lazy-tool-transport.d.ts +10 -0
  27. package/dist/{localSources-SSIEH5F5.js → localSources-I6SIQ6CX.js} +6 -6
  28. package/dist/model-call.js +3 -3
  29. package/dist/sandbox/browser-control-client.d.ts +77 -0
  30. package/dist/sandbox/index.d.ts +1 -1
  31. package/dist/sandbox/index.js +7 -7
  32. package/dist/sandbox/selfhosted/session.d.ts +3 -0
  33. package/dist/slack-rest-mcp.js +8 -8
  34. package/dist/workspace-tool-gateway.js +12 -12
  35. package/package.json +14 -14
  36. package/src/browser-current-tab.ts +139 -0
  37. package/src/browser-model-text.ts +378 -0
  38. package/src/codex-tool-search.ts +53 -0
  39. package/src/index.ts +39 -1
  40. package/src/interaction-tool-json-schema.ts +281 -0
  41. package/src/interaction-tools.ts +425 -235
  42. package/src/lazy-tool-transport.ts +229 -10
  43. package/src/model-input.ts +12 -1
  44. package/src/sandbox/browser-control-client.ts +336 -0
  45. package/src/sandbox/index.ts +4 -0
  46. package/src/sandbox/selfhosted/session.ts +38 -0
  47. package/dist/agent-CTPLA3HP.js +0 -9
  48. package/dist/browser-action-json-schema.d.ts +0 -4
  49. package/dist/chunk-7OUG7WBV.js.map +0 -1
  50. package/dist/chunk-EK7IW2FR.js.map +0 -1
  51. package/dist/chunk-XRX57ZQO.js.map +0 -1
  52. package/src/browser-action-json-schema.ts +0 -10
  53. /package/dist/{agent-CTPLA3HP.js.map → agent-LJT5NPWT.js.map} +0 -0
  54. /package/dist/{chunk-HJSIPTFA.js.map → chunk-4XDJ4TJA.js.map} +0 -0
  55. /package/dist/{chunk-XQ23VCZ4.js.map → chunk-6FQ6KEII.js.map} +0 -0
  56. /package/dist/{chunk-7LJ7KDPI.js.map → chunk-ERD4ICVB.js.map} +0 -0
  57. /package/dist/{chunk-YG26XE7B.js.map → chunk-JYDG2A7H.js.map} +0 -0
  58. /package/dist/{chunk-RSHQSLEJ.js.map → chunk-KIL4FGX5.js.map} +0 -0
  59. /package/dist/{chunk-NWS4HPB7.js.map → chunk-MIAL5I4O.js.map} +0 -0
  60. /package/dist/{chunk-Z5U6WE22.js.map → chunk-PRZPGRIK.js.map} +0 -0
  61. /package/dist/{chunk-KCPW4K7L.js.map → chunk-PZQUUBEB.js.map} +0 -0
  62. /package/dist/{chunk-5WJOPANS.js.map → chunk-Y5THZH5N.js.map} +0 -0
  63. /package/dist/{lazy-manifest-AJ6XPJ4F.js.map → lazy-manifest-BLZ3UZLX.js.map} +0 -0
  64. /package/dist/{localSources-SSIEH5F5.js.map → localSources-I6SIQ6CX.js.map} +0 -0
@@ -12,6 +12,9 @@ import {
12
12
  } from "@openai/agents";
13
13
  import {
14
14
  isHarnessControlMcpFunctionTool,
15
+ interactionEntryCompanions,
16
+ isInteractionEntryFunctionTool,
17
+ isInteractionEntryToolName,
15
18
  isSearchableMcpFunctionTool,
16
19
  searchToolPool,
17
20
  } from "./codex-tool-search";
@@ -58,6 +61,9 @@ const ALWAYS_VISIBLE_BASE_TOOL_NAMES: ReadonlySet<string> = new Set([
58
61
  ]);
59
62
  const DISPATCH_MARKER_KEY = "opengeni.lazy_dispatch.v1";
60
63
  const SEARCH_MARKER_KEY = "opengeni.lazy_search.v1";
64
+ /** Frozen companion schemas an entry-tool call disclosed, kept on the call itself. */
65
+ const FAMILY_DISCLOSURE_MARKER_KEY = "opengeni.family_disclosure.v1";
66
+ const FAMILY_DISCLOSURE_CALL_PREFIX = "opengeni:family:";
61
67
  const INTERNAL_REGISTRATION_TOOL_MARKER_KEY = "opengeni.internal_lazy_registration.v1";
62
68
  const INTERNAL_DISPATCH_REGISTRATION_CALL_PREFIX = "opengeni:lazy-dispatch:register:";
63
69
 
@@ -124,6 +130,18 @@ type GenericSearchMarker = {
124
130
  output: string;
125
131
  };
126
132
 
133
+ /**
134
+ * Written once when the model calls an interaction entry tool (browser_open,
135
+ * computer_open). The schemas are frozen at that moment so the disclosure the
136
+ * provider sees after the open result never changes on later requests, even
137
+ * after a deploy changes the live catalog.
138
+ */
139
+ type FamilyDisclosureMarker = {
140
+ version: 1;
141
+ format: "native" | "generic";
142
+ tools: Record<string, unknown>[];
143
+ };
144
+
127
145
  type FunctionCallItem = {
128
146
  type: "function_call";
129
147
  callId: string;
@@ -339,6 +357,10 @@ export class LazyToolRuntime {
339
357
  // polling, wait_for_input) stay visible on every transport when the
340
358
  // authorized opengeni server listed them; its other tools stay deferred.
341
359
  if (isHarnessControlMcpFunctionTool(tool, this.mcpServerIds, modelServerIds)) continue;
360
+ // Each interaction family's entry tool (browser_open, computer_open)
361
+ // stays visible so a browser or computer task starts without a search
362
+ // round trip; calling it discloses the rest of the family.
363
+ if (isInteractionEntryFunctionTool(tool, this.mcpServerIds, modelServerIds)) continue;
342
364
  // Origin, not transport: deferred MCP plus every non-MCP function tool
343
365
  // outside the base set. ToolRef.eager still decides the MCP arm.
344
366
  const lazy =
@@ -348,9 +370,10 @@ export class LazyToolRuntime {
348
370
  // After the preparation fence, deferred tools stay off the Agent
349
371
  // list. Search teaches names; a remembered raw name binds later
350
372
  // through resolveMissingFunctionTool. Do not use the SDK's
351
- // deferLoading gate — installLazyToolRuntime clears it. Browser,
352
- // Computer, and image/video schemas hide behind search on every
353
- // transport; the base set stays in the first request.
373
+ // deferLoading gate — installLazyToolRuntime clears it. Browser and
374
+ // Computer tools other than their entry tools, and image/video
375
+ // schemas, hide behind search on every transport; the base set stays
376
+ // in the first request.
354
377
  tool.deferLoading = false;
355
378
  this.searchableToolNames.add(tool.name);
356
379
  }
@@ -445,6 +468,14 @@ export class LazyToolRuntime {
445
468
  return searchToolPool(this.searchableTools(this.currentTools), rawArguments);
446
469
  }
447
470
 
471
+ /** Model-visible schemas an entry-tool call discloses, or [] for any other tool. */
472
+ familyDisclosure(toolName: string): Record<string, unknown>[] {
473
+ if (!isInteractionEntryToolName(toolName)) return [];
474
+ return interactionEntryCompanions(toolName, this.searchableTools(this.currentTools))
475
+ .map((tool) => modelVisibleToolDefinition(tool))
476
+ .filter((definition): definition is Record<string, unknown> => definition !== null);
477
+ }
478
+
448
479
  genericSearchOutput(rawArguments: unknown): string {
449
480
  const definitions: Record<string, unknown>[] = [];
450
481
  for (const tool of this.search(rawArguments)) {
@@ -720,6 +751,7 @@ function restoredProviderData(providerData: unknown): Record<string, unknown> |
720
751
  const restored = { ...providerData };
721
752
  delete restored[DISPATCH_MARKER_KEY];
722
753
  delete restored[SEARCH_MARKER_KEY];
754
+ delete restored[FAMILY_DISCLOSURE_MARKER_KEY];
723
755
  return Object.keys(restored).length > 0 ? restored : undefined;
724
756
  }
725
757
 
@@ -750,7 +782,9 @@ export function restoreGenericDispatchHistoryItem<T>(candidate: T): T {
750
782
  const searchMarker = providerData?.[SEARCH_MARKER_KEY];
751
783
  const hasSearchMarker =
752
784
  isRecord(searchMarker) && searchMarker.version === 1 && typeof searchMarker.output === "string";
753
- if (!hasDispatchMarker && !hasSearchMarker) {
785
+ const hasFamilyMarker =
786
+ providerData !== undefined && FAMILY_DISCLOSURE_MARKER_KEY in providerData;
787
+ if (!hasDispatchMarker && !hasSearchMarker && !hasFamilyMarker) {
754
788
  return candidate;
755
789
  }
756
790
  const restored = { ...candidate } as Record<string, unknown>;
@@ -896,6 +930,192 @@ export function transformGenericDispatchResponse(
896
930
  };
897
931
  }
898
932
 
933
+ /** Apply the transport's response rewrites to one model output item. */
934
+ function transformResponseItem(item: unknown, runtime: LazyToolRuntime): unknown[] {
935
+ const items =
936
+ runtime.transport === "generic_dispatch" ? transformGenericDispatchCall(item, runtime) : [item];
937
+ return items.map((candidate) => withFamilyDisclosure(candidate, runtime));
938
+ }
939
+
940
+ function transformResponseOutput(
941
+ output: ModelResponse["output"],
942
+ runtime: LazyToolRuntime,
943
+ ): ModelResponse["output"] {
944
+ const transformed = output.flatMap((item) => transformResponseItem(item, runtime));
945
+ const unchanged =
946
+ transformed.length === output.length &&
947
+ transformed.every((item, index) => item === output[index]);
948
+ return unchanged ? output : (transformed as ModelResponse["output"]);
949
+ }
950
+
951
+ /**
952
+ * Freeze the family an entry-tool call discloses onto the call. The provider
953
+ * sees the disclosure right after that call's result (see
954
+ * expandInteractionFamilyDisclosures), so the model's next step can observe
955
+ * and act without first searching for the tools.
956
+ */
957
+ function withFamilyDisclosure(candidate: unknown, runtime: LazyToolRuntime): unknown {
958
+ if (!isRecord(candidate) || candidate.type !== "function_call") return candidate;
959
+ if (typeof candidate.name !== "string" || !isInteractionEntryToolName(candidate.name)) {
960
+ return candidate;
961
+ }
962
+ const providerData = isRecord(candidate.providerData) ? candidate.providerData : {};
963
+ if (FAMILY_DISCLOSURE_MARKER_KEY in providerData) return candidate;
964
+ const tools = runtime.familyDisclosure(candidate.name);
965
+ if (tools.length === 0) return candidate;
966
+ return {
967
+ ...candidate,
968
+ providerData: {
969
+ ...providerData,
970
+ [FAMILY_DISCLOSURE_MARKER_KEY]: {
971
+ version: 1,
972
+ format: runtime.transport === "generic_dispatch" ? "generic" : "native",
973
+ tools,
974
+ } satisfies FamilyDisclosureMarker,
975
+ },
976
+ };
977
+ }
978
+
979
+ function familyDisclosureMarker(candidate: Record<string, unknown>): FamilyDisclosureMarker | null {
980
+ const providerData = isRecord(candidate.providerData) ? candidate.providerData : null;
981
+ const marker = providerData?.[FAMILY_DISCLOSURE_MARKER_KEY];
982
+ if (
983
+ !isRecord(marker) ||
984
+ marker.version !== 1 ||
985
+ (marker.format !== "native" && marker.format !== "generic") ||
986
+ !Array.isArray(marker.tools)
987
+ ) {
988
+ return null;
989
+ }
990
+ const tools = marker.tools.filter(
991
+ (tool): tool is Record<string, unknown> => isRecord(tool) && typeof tool.name === "string",
992
+ );
993
+ return { version: 1, format: marker.format, tools };
994
+ }
995
+
996
+ function disclosedToolNames(tools: unknown): string[] {
997
+ if (!Array.isArray(tools)) return [];
998
+ return tools.flatMap((tool) =>
999
+ isRecord(tool) && typeof tool.name === "string" ? [tool.name] : [],
1000
+ );
1001
+ }
1002
+
1003
+ function genericSearchOutputNames(output: string): string[] {
1004
+ const parsed = parseJsonObject(output);
1005
+ return parsed ? disclosedToolNames(parsed.tools) : [];
1006
+ }
1007
+
1008
+ /**
1009
+ * Show each entry-tool call's frozen family disclosure right after its result,
1010
+ * as the same search call and output a model-issued search would have produced
1011
+ * (native tool_search items, or a generic tool_search function call). Tools the
1012
+ * history already disclosed are not repeated. The expansion depends only on
1013
+ * earlier items, so every later request reproduces the identical prefix.
1014
+ */
1015
+ export function expandInteractionFamilyDisclosures<T>(input: T[]): T[] {
1016
+ const pending = new Map<string, FamilyDisclosureMarker>();
1017
+ const disclosed = new Set<string>();
1018
+ let expanded: T[] | null = null;
1019
+ for (let index = 0; index < input.length; index += 1) {
1020
+ const original = input[index]!;
1021
+ expanded?.push(original);
1022
+ const candidate: unknown = original;
1023
+ if (!isRecord(candidate)) continue;
1024
+ if (candidate.type === "tool_search_output") {
1025
+ for (const name of disclosedToolNames(candidate.tools)) disclosed.add(name);
1026
+ continue;
1027
+ }
1028
+ if (candidate.type === "function_call") {
1029
+ const providerData = isRecord(candidate.providerData) ? candidate.providerData : null;
1030
+ const searchMarker = providerData?.[SEARCH_MARKER_KEY];
1031
+ if (isRecord(searchMarker) && typeof searchMarker.output === "string") {
1032
+ for (const name of genericSearchOutputNames(searchMarker.output)) disclosed.add(name);
1033
+ }
1034
+ const id = callId(candidate);
1035
+ const marker = familyDisclosureMarker(candidate);
1036
+ if (id && marker) pending.set(id, marker);
1037
+ continue;
1038
+ }
1039
+ if (candidate.type === "function_call_result" && candidate.name === TOOL_SEARCH_NAME) {
1040
+ if (typeof candidate.output === "string") {
1041
+ for (const name of genericSearchOutputNames(candidate.output)) disclosed.add(name);
1042
+ }
1043
+ continue;
1044
+ }
1045
+ if (candidate.type !== "function_call_result") continue;
1046
+ const id = callId(candidate);
1047
+ const marker = id ? pending.get(id) : undefined;
1048
+ if (!id || !marker) continue;
1049
+ pending.delete(id);
1050
+ const tools = marker.tools.filter((tool) => !disclosed.has(String(tool.name)));
1051
+ if (tools.length === 0) continue;
1052
+ for (const tool of tools) disclosed.add(String(tool.name));
1053
+ expanded ??= input.slice(0, index + 1);
1054
+ expanded.push(...(cachedFamilyDisclosureItems(candidate, id, marker.format, tools) as T[]));
1055
+ }
1056
+ return expanded ?? input;
1057
+ }
1058
+
1059
+ /**
1060
+ * The SDK reuses item identities between requests. Returning the same synthetic
1061
+ * objects for the same result keeps later identity-memoized projections and
1062
+ * their append-only prefix reuse effective.
1063
+ */
1064
+ const familyDisclosureItemCache = new WeakMap<
1065
+ object,
1066
+ { key: string; items: Record<string, unknown>[] }
1067
+ >();
1068
+
1069
+ function cachedFamilyDisclosureItems(
1070
+ result: Record<string, unknown>,
1071
+ id: string,
1072
+ format: FamilyDisclosureMarker["format"],
1073
+ tools: Record<string, unknown>[],
1074
+ ): Record<string, unknown>[] {
1075
+ const key = `${format}\u0000${tools.map((tool) => String(tool.name)).join("\u0000")}`;
1076
+ const cached = familyDisclosureItemCache.get(result);
1077
+ if (cached?.key === key) return cached.items;
1078
+ const items = familyDisclosureItems(`${FAMILY_DISCLOSURE_CALL_PREFIX}${id}`, format, tools);
1079
+ familyDisclosureItemCache.set(result, { key, items });
1080
+ return items;
1081
+ }
1082
+
1083
+ function familyDisclosureItems(
1084
+ id: string,
1085
+ format: FamilyDisclosureMarker["format"],
1086
+ tools: Record<string, unknown>[],
1087
+ ): Record<string, unknown>[] {
1088
+ const names = tools.map((tool) => String(tool.name));
1089
+ if (format === "native") {
1090
+ return [
1091
+ {
1092
+ type: "tool_search_call",
1093
+ call_id: id,
1094
+ status: "completed",
1095
+ execution: "client",
1096
+ arguments: { query: "", names },
1097
+ },
1098
+ { type: "tool_search_output", call_id: id, status: "completed", execution: "client", tools },
1099
+ ];
1100
+ }
1101
+ return [
1102
+ {
1103
+ type: "function_call",
1104
+ callId: id,
1105
+ name: TOOL_SEARCH_NAME,
1106
+ arguments: JSON.stringify({ query: "", names }),
1107
+ status: "completed",
1108
+ },
1109
+ {
1110
+ type: "function_call_result",
1111
+ callId: id,
1112
+ name: TOOL_SEARCH_NAME,
1113
+ status: "completed",
1114
+ output: JSON.stringify({ tools }),
1115
+ },
1116
+ ];
1117
+ }
1118
+
899
1119
  function prepareLazyToolRequest(request: ModelRequest, runtime: LazyToolRuntime): ModelRequest {
900
1120
  const input = restoreGenericSearchResults(request.input);
901
1121
  return {
@@ -922,9 +1142,7 @@ class LazyToolModel implements Model {
922
1142
  if (responseRequiresToolPreparation(response, this.runtime)) {
923
1143
  await this.runtime.ensurePrepared();
924
1144
  }
925
- return this.runtime.transport === "generic_dispatch"
926
- ? transformGenericDispatchResponse(response, this.runtime)
927
- : response;
1145
+ return { ...response, output: transformResponseOutput(response.output, this.runtime) };
928
1146
  }
929
1147
 
930
1148
  async *getStreamedResponse(request: ModelRequest): AsyncIterable<StreamEvent> {
@@ -939,13 +1157,14 @@ class LazyToolModel implements Model {
939
1157
  }
940
1158
  }
941
1159
  const delivered =
942
- this.runtime.transport === "generic_dispatch" && event.type === "response_done"
1160
+ event.type === "response_done"
943
1161
  ? ({
944
1162
  ...event,
945
1163
  response: {
946
1164
  ...event.response,
947
- output: event.response.output.flatMap((item) =>
948
- transformGenericDispatchCall(item, this.runtime),
1165
+ output: transformResponseOutput(
1166
+ event.response.output as ModelResponse["output"],
1167
+ this.runtime,
949
1168
  ) as typeof event.response.output,
950
1169
  },
951
1170
  } as StreamEvent)
@@ -17,6 +17,7 @@ import {
17
17
  repairHistoryProtocolItems,
18
18
  } from "./history-sanitizer";
19
19
  import {
20
+ expandInteractionFamilyDisclosures,
20
21
  restoreGenericDispatchHistoryItem,
21
22
  restoreGenericDispatchHistoryItems,
22
23
  } from "./lazy-tool-transport";
@@ -500,7 +501,7 @@ export function callModelInputFilterForSettings(
500
501
  /** Rules that normalize history but do not impose final bounds/accounting. */
501
502
  export function baseModelInputFilterForSettings(settings: Settings): CallModelInputFilter {
502
503
  const stripProviderIds = settings.openaiProviderItemIds === "strip";
503
- return memoizedInputItemProjectionFilter(
504
+ const projectItems = memoizedInputItemProjectionFilter(
504
505
  (source) => {
505
506
  let item = restoreGenericDispatchHistoryItem(source);
506
507
  item = normalizeComputerCallAction(
@@ -519,4 +520,14 @@ export function baseModelInputFilterForSettings(settings: Settings): CallModelIn
519
520
  // latest model/tool step instead of walking a six-figure history again.
520
521
  { appendOnlyInput: true },
521
522
  );
523
+ return composeCallModelInputFilters([interactionFamilyDisclosureFilter, projectItems]);
522
524
  }
525
+
526
+ /**
527
+ * Show an entry-tool call's frozen family disclosure after its result. Runs
528
+ * before the item projection, which removes the marker from the call.
529
+ */
530
+ const interactionFamilyDisclosureFilter: CallModelInputFilter = ({ modelData }) => {
531
+ const input = expandInteractionFamilyDisclosures(modelData.input);
532
+ return input === modelData.input ? modelData : { ...modelData, input };
533
+ };