@opengeni/codemode 0.4.22 → 0.4.27-canary.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.
package/dist/index.js CHANGED
@@ -1,23 +1,29 @@
1
1
  // src/index.ts
2
- import { createHash, randomUUID } from "crypto";
3
- import Ajv from "ajv";
4
- import Ajv2019 from "ajv/dist/2019.js";
5
- import Ajv2020 from "ajv/dist/2020.js";
2
+ import { randomUUID as randomUUID2 } from "crypto";
6
3
  import {
7
4
  ATTEMPT_TOOL_CATALOG_VERSION,
8
5
  ATTEMPT_TOOL_CATALOG_MAX_BYTES,
9
6
  AttemptToolCall,
10
7
  AttemptToolCatalog,
11
- AttemptToolCatalogEntry,
12
8
  AttemptToolResult,
13
9
  CodemodeCallSubmission,
14
10
  CodemodeOperation,
15
11
  CodemodeDispatchAck,
16
12
  CodemodeDispatchRequest,
17
- OPENGENI_API_CONTRACT_HEADER,
18
- OPENGENI_API_CONTRACT_REVISION,
19
- isToolResultSpilledReceipt
13
+ AttemptToolCaller
20
14
  } from "@opengeni/contracts";
15
+ import {
16
+ ToolGatewayApprovalRequiredError as AttemptToolApprovalRequiredError,
17
+ ToolGatewayCatalogIntegrityError as AttemptToolCatalogIntegrityError,
18
+ ToolGatewayCatalogStaleError as AttemptToolCatalogStaleError,
19
+ ToolGatewayCatalogTooLargeError as AttemptToolCatalogTooLargeError,
20
+ ToolGatewayInputValidationError as AttemptToolInputValidationError,
21
+ ToolGatewayOutputValidationError as AttemptToolOutputValidationError,
22
+ ToolGatewayPathCollisionError as AttemptToolPathCollisionError,
23
+ ToolGatewayToolNotFoundError as AttemptToolNotFoundError,
24
+ digestCanonicalJson,
25
+ prepareToolGatewayDefinitions
26
+ } from "@opengeni/tool-gateway";
21
27
 
22
28
  // src/environment.ts
23
29
  import { readFile } from "fs/promises";
@@ -1067,314 +1073,138 @@ function createOpenGeniCodemode(client = () => environmentCodemodeClient()) {
1067
1073
  var openGeni = createOpenGeniCodemode();
1068
1074
 
1069
1075
  // src/declarations.ts
1076
+ import { generateToolDeclarations, jsonSchemaToTypeScript } from "@opengeni/tool-gateway";
1070
1077
  function generateCodemodeDeclarations(catalog, options = {}) {
1071
1078
  const verified = parseVerifiedAttemptToolCatalog(catalog);
1072
- const moduleSpecifier = options.moduleSpecifier ?? "@opengeni/codemode";
1073
- const root = namespaceNode();
1074
- for (const entry of verified.entries) insertEntry(root, entry);
1075
- return [
1076
- "// Generated by @opengeni/codemode. Do not edit.",
1077
- `// Attempt catalog digest: ${verified.digest}`,
1078
- `import type { CodemodeCallOptions, CodemodeToolResult } from ${JSON.stringify(moduleSpecifier)};`,
1079
- "",
1080
- `declare module ${JSON.stringify(moduleSpecifier)} {`,
1081
- " interface CodemodeGeneratedTools {",
1082
- ...renderChildren(root, 4),
1083
- " }",
1084
- "}",
1085
- "",
1086
- "export {};",
1087
- ""
1088
- ].join("\n");
1089
- }
1090
- function jsonSchemaToTypeScript(schema) {
1091
- return schemaType(schema, schema, /* @__PURE__ */ new Set(), 0);
1092
- }
1093
- function namespaceNode() {
1094
- return { children: /* @__PURE__ */ new Map(), entry: null };
1095
- }
1096
- function insertEntry(root, entry) {
1097
- let node = root;
1098
- for (const [index, segment] of entry.codemodePath.entries()) {
1099
- if (node.entry) {
1100
- throw new Error(
1101
- `Codemode declaration path ${entry.codemodePath.join(".")} extends a tool leaf`
1102
- );
1103
- }
1104
- let child = node.children.get(segment);
1105
- if (!child) {
1106
- child = namespaceNode();
1107
- node.children.set(segment, child);
1108
- }
1109
- node = child;
1110
- if (index === entry.codemodePath.length - 1) {
1111
- if (node.entry || node.children.size > 0) {
1112
- throw new Error(`Codemode declaration path ${entry.codemodePath.join(".")} collides`);
1113
- }
1114
- node.entry = entry;
1115
- }
1116
- }
1117
- }
1118
- function renderChildren(node, indent) {
1119
- const lines = [];
1120
- for (const [name, child] of [...node.children].sort(
1121
- ([left], [right]) => left.localeCompare(right)
1122
- )) {
1123
- if (child.entry) {
1124
- lines.push(...renderTool(name, child.entry, indent));
1125
- continue;
1079
+ return generateToolDeclarations(
1080
+ { digest: verified.digest, entries: verified.entries },
1081
+ {
1082
+ moduleSpecifier: options.moduleSpecifier ?? "@opengeni/codemode",
1083
+ interfaceName: "CodemodeGeneratedTools",
1084
+ callOptionsType: "CodemodeCallOptions",
1085
+ fallbackResultType: "CodemodeToolResult",
1086
+ generatedBy: "@opengeni/codemode via @opengeni/tool-gateway",
1087
+ catalogDigestLabel: "Attempt catalog digest"
1126
1088
  }
1127
- lines.push(`${spaces(indent)}readonly ${name}: {`);
1128
- lines.push(...renderChildren(child, indent + 2));
1129
- lines.push(`${spaces(indent)}};`);
1130
- }
1131
- return lines;
1132
- }
1133
- function renderTool(name, entry, indent) {
1134
- const input = schemaType(entry.inputSchema, entry.inputSchema, /* @__PURE__ */ new Set(), 0);
1135
- const output = entry.outputSchema ? schemaType(entry.outputSchema, entry.outputSchema, /* @__PURE__ */ new Set(), 0) : "CodemodeToolResult";
1136
- const optionalArguments = rootObjectArgumentsAreOptional(entry.inputSchema);
1137
- const description = boundedDoc(entry.description ?? entry.title);
1138
- return [
1139
- ...description ? renderDoc(description, indent) : [],
1140
- `${spaces(indent)}readonly ${name}: (`,
1141
- `${spaces(indent + 2)}argumentsValue${optionalArguments ? "?" : ""}: ${input},`,
1142
- `${spaces(indent + 2)}options?: CodemodeCallOptions,`,
1143
- `${spaces(indent)}) => Promise<${output}>;`
1144
- ];
1145
- }
1146
- function rootObjectArgumentsAreOptional(schema) {
1147
- if (!isSchemaObject(schema)) return false;
1148
- const required = Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : [];
1149
- return required.length === 0 && (schema.type === "object" || isSchemaObject(schema.properties));
1150
- }
1151
- function schemaType(schema, rootSchema, resolvingRefs, depth) {
1152
- if (depth > 48 || schema === true) return "unknown";
1153
- if (schema === false) return "never";
1154
- if (!isSchemaObject(schema)) return "unknown";
1155
- if (typeof schema.$ref === "string") {
1156
- const reference = schema.$ref;
1157
- if (!reference.startsWith("#/") || resolvingRefs.has(reference)) return "unknown";
1158
- const resolved = resolveLocalReference(rootSchema, reference);
1159
- if (resolved === void 0) return "unknown";
1160
- const next = new Set(resolvingRefs);
1161
- next.add(reference);
1162
- return schemaType(resolved, rootSchema, next, depth + 1);
1163
- }
1164
- if (Object.hasOwn(schema, "const")) return literalType(schema.const);
1165
- if (Array.isArray(schema.enum)) {
1166
- return union(schema.enum.map(literalType));
1167
- }
1168
- const composites = [];
1169
- if (Array.isArray(schema.oneOf)) {
1170
- composites.push(
1171
- union(schema.oneOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1)))
1172
- );
1173
- }
1174
- if (Array.isArray(schema.anyOf)) {
1175
- composites.push(
1176
- union(schema.anyOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1)))
1177
- );
1178
- }
1179
- if (Array.isArray(schema.allOf)) {
1180
- composites.push(
1181
- intersection(
1182
- schema.allOf.map((part) => schemaType(part, rootSchema, resolvingRefs, depth + 1))
1183
- )
1184
- );
1185
- }
1186
- if (composites.length > 0) {
1187
- const composed = intersection(composites);
1188
- return schema.nullable === true ? union([composed, "null"]) : composed;
1189
- }
1190
- const declaredTypes = Array.isArray(schema.type) ? schema.type.filter((value) => typeof value === "string") : typeof schema.type === "string" ? [schema.type] : inferredSchemaTypes(schema);
1191
- const rendered = declaredTypes.map(
1192
- (type) => typeType(type, schema, rootSchema, resolvingRefs, depth + 1)
1193
1089
  );
1194
- if (schema.nullable === true) rendered.push("null");
1195
- return union(rendered.length > 0 ? rendered : ["unknown"]);
1196
- }
1197
- function inferredSchemaTypes(schema) {
1198
- if (isSchemaObject(schema.properties) || Object.hasOwn(schema, "additionalProperties")) {
1199
- return ["object"];
1200
- }
1201
- if (Object.hasOwn(schema, "items") || Array.isArray(schema.prefixItems)) return ["array"];
1202
- return [];
1203
- }
1204
- function typeType(type, schema, rootSchema, resolvingRefs, depth) {
1205
- switch (type) {
1206
- case "null":
1207
- return "null";
1208
- case "boolean":
1209
- return "boolean";
1210
- case "integer":
1211
- case "number":
1212
- return "number";
1213
- case "string":
1214
- return "string";
1215
- case "array":
1216
- return arrayType(schema, rootSchema, resolvingRefs, depth);
1217
- case "object":
1218
- return objectType(schema, rootSchema, resolvingRefs, depth);
1219
- default:
1220
- return "unknown";
1221
- }
1222
- }
1223
- function arrayType(schema, rootSchema, resolvingRefs, depth) {
1224
- if (Array.isArray(schema.prefixItems)) {
1225
- const tuple = schema.prefixItems.map(
1226
- (item2) => schemaType(item2, rootSchema, resolvingRefs, depth + 1)
1227
- );
1228
- if (schema.items === false) return `readonly [${tuple.join(", ")}]`;
1229
- const rest = schema.items === void 0 || schema.items === true ? "unknown" : schemaType(schema.items, rootSchema, resolvingRefs, depth + 1);
1230
- return `readonly [${tuple.join(", ")}${tuple.length > 0 ? ", " : ""}...${rest}[]]`;
1231
- }
1232
- const item = schema.items === void 0 || schema.items === true ? "unknown" : schemaType(schema.items, rootSchema, resolvingRefs, depth + 1);
1233
- return `readonly (${item})[]`;
1234
1090
  }
1235
- function objectType(schema, rootSchema, resolvingRefs, depth) {
1236
- const properties = isSchemaObject(schema.properties) ? schema.properties : {};
1237
- const required = new Set(
1238
- Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []
1239
- );
1240
- const entries = Object.entries(properties).sort(([left], [right]) => left.localeCompare(right));
1241
- const fields = entries.map(([name, propertySchema]) => {
1242
- const key = identifierOrQuoted(name);
1243
- const optional = required.has(name) ? "" : "?";
1244
- return `readonly ${key}${optional}: ${schemaType(
1245
- propertySchema,
1246
- rootSchema,
1247
- resolvingRefs,
1248
- depth + 1
1249
- )}`;
1250
- });
1251
- for (const missing of [...required].filter((name) => !Object.hasOwn(properties, name)).sort()) {
1252
- fields.push(`readonly ${identifierOrQuoted(missing)}: unknown`);
1253
- }
1254
- const additional = schema.additionalProperties;
1255
- if (additional !== false) {
1256
- if (entries.length === 0 && additional !== void 0 && additional !== true) {
1257
- return `Readonly<Record<string, ${schemaType(
1258
- additional,
1259
- rootSchema,
1260
- resolvingRefs,
1261
- depth + 1
1262
- )}>>`;
1091
+
1092
+ // src/site.ts
1093
+ import { randomUUID } from "crypto";
1094
+ import { generateToolDeclarations as generateToolDeclarations2 } from "@opengeni/tool-gateway";
1095
+ var CODEMODE_SITE_LOCAL_PATH = "/__opengeni/site-tools";
1096
+ function createCodemodeSiteRequestHandler(client = () => environmentCodemodeClient()) {
1097
+ const provide = typeof client === "function" ? client : () => client;
1098
+ return async (request) => {
1099
+ try {
1100
+ const active = await provide();
1101
+ const pathname = new URL(request.url).pathname;
1102
+ if (request.method === "GET" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/catalog`) {
1103
+ const catalog = await active.catalog({ signal: request.signal });
1104
+ return Response.json(projectSiteCatalog(catalog));
1105
+ }
1106
+ if (request.method === "GET" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/declarations`) {
1107
+ const catalog = await active.catalog({ signal: request.signal });
1108
+ return Response.json({
1109
+ catalogDigest: catalog.digest,
1110
+ moduleSpecifier: "@opengeni/sdk",
1111
+ source: generateToolDeclarations2(
1112
+ { digest: catalog.digest, entries: catalog.entries },
1113
+ {
1114
+ moduleSpecifier: "@opengeni/sdk",
1115
+ interfaceName: "OpenGeniGeneratedTools",
1116
+ callOptionsType: "OpenGeniToolCallOptions",
1117
+ fallbackResultType: "ToolGatewayResult",
1118
+ generatedBy: "@opengeni/codemode local Site preview",
1119
+ catalogDigestLabel: "Attempt catalog digest"
1120
+ }
1121
+ )
1122
+ });
1123
+ }
1124
+ if (request.method === "POST" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/calls`) {
1125
+ const body = await request.json();
1126
+ if (!isSiteCall(body)) return siteError(400, "invalid_request", "Invalid Site tool call");
1127
+ const catalog = await active.catalog({ signal: request.signal });
1128
+ if (body.catalogDigest !== catalog.digest) {
1129
+ return siteError(409, "catalog_stale", "The local Site tool catalog changed", true);
1130
+ }
1131
+ const operationId = body.operationId ?? randomUUID();
1132
+ const result = await active.call(body.identity, body.arguments, {
1133
+ operationId,
1134
+ signal: request.signal
1135
+ });
1136
+ return Response.json({
1137
+ operationId,
1138
+ catalogDigest: catalog.digest,
1139
+ result
1140
+ });
1141
+ }
1142
+ return siteError(404, "not_found", "Local Site tool endpoint not found");
1143
+ } catch (error) {
1144
+ if (error instanceof CodemodeTransportError) {
1145
+ return siteError(
1146
+ error.status && error.status >= 400 && error.status <= 599 ? error.status : 502,
1147
+ error.remoteCode ?? error.code,
1148
+ error.message,
1149
+ error.retryable === true,
1150
+ error.outcomeUnknown === true
1151
+ );
1152
+ }
1153
+ return siteError(
1154
+ 500,
1155
+ "local_codemode_error",
1156
+ error instanceof Error ? error.message : "Local Site tool request failed"
1157
+ );
1263
1158
  }
1264
- fields.push("readonly [key: string]: unknown");
1265
- }
1266
- return fields.length === 0 ? "Record<string, never>" : `{ ${fields.join("; ")} }`;
1267
- }
1268
- function resolveLocalReference(rootSchema, reference) {
1269
- let current = rootSchema;
1270
- for (const encoded of reference.slice(2).split("/")) {
1271
- if (!isSchemaObject(current)) return void 0;
1272
- const segment = encoded.replace(/~1/gu, "/").replace(/~0/gu, "~");
1273
- if (!Object.hasOwn(current, segment)) return void 0;
1274
- current = current[segment];
1275
- }
1276
- return current;
1277
- }
1278
- function literalType(value) {
1279
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1280
- return JSON.stringify(value);
1281
- }
1282
- if (Array.isArray(value)) return `readonly [${value.map(literalType).join(", ")}]`;
1283
- if (isSchemaObject(value)) {
1284
- return `{ ${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `readonly ${identifierOrQuoted(key)}: ${literalType(child)}`).join("; ")} }`;
1285
- }
1286
- return "unknown";
1287
- }
1288
- function union(types) {
1289
- const unique = [...new Set(types)];
1290
- if (unique.includes("unknown")) return "unknown";
1291
- if (unique.length === 0) return "never";
1292
- return unique.length === 1 ? unique[0] : unique.map(parenthesizeComposite).join(" | ");
1293
- }
1294
- function intersection(types) {
1295
- const unique = [...new Set(types.filter((type) => type !== "unknown"))];
1296
- if (unique.length === 0) return "unknown";
1297
- return unique.length === 1 ? unique[0] : unique.map(parenthesizeComposite).join(" & ");
1298
- }
1299
- function parenthesizeComposite(type) {
1300
- return /[|&]/u.test(type) ? `(${type})` : type;
1301
- }
1302
- function identifierOrQuoted(value) {
1303
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(value) ? value : JSON.stringify(value);
1159
+ };
1304
1160
  }
1305
- function boundedDoc(value) {
1306
- if (!value) return null;
1307
- const normalized = value.replace(/\s+/gu, " ").trim().replace(/\*\//gu, "*\\/");
1308
- if (!normalized) return null;
1309
- return normalized.length <= 512 ? normalized : `${normalized.slice(0, 509)}...`;
1161
+ function projectSiteCatalog(catalog) {
1162
+ return {
1163
+ version: catalog.version,
1164
+ generation: catalog.generation,
1165
+ digest: catalog.digest,
1166
+ createdAt: catalog.createdAt,
1167
+ entries: catalog.entries
1168
+ };
1310
1169
  }
1311
- function renderDoc(value, indent) {
1312
- return [`${spaces(indent)}/** ${value} */`];
1170
+ function isSiteCall(value) {
1171
+ if (!isRecord(value) || typeof value.catalogDigest !== "string") return false;
1172
+ if (value.operationId !== void 0 && typeof value.operationId !== "string") return false;
1173
+ return isRecord(value.identity) && typeof value.identity.serverId === "string" && typeof value.identity.toolName === "string" && isRecord(value.arguments);
1313
1174
  }
1314
- function spaces(count) {
1315
- return " ".repeat(count);
1175
+ function siteError(status, code, message, retryable = false, outcomeUnknown = false) {
1176
+ return Response.json(
1177
+ {
1178
+ error: {
1179
+ code,
1180
+ message,
1181
+ retryable,
1182
+ ...outcomeUnknown ? { outcomeUnknown: true } : {}
1183
+ }
1184
+ },
1185
+ { status }
1186
+ );
1316
1187
  }
1317
- function isSchemaObject(value) {
1318
- return value !== null && typeof value === "object" && !Array.isArray(value);
1188
+ function isRecord(value) {
1189
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1319
1190
  }
1320
1191
 
1321
1192
  // src/index.ts
1322
- var AttemptToolCatalogStaleError = class extends Error {
1323
- code = "catalog_stale";
1324
- constructor() {
1325
- super("Codemode catalog is stale for the active execution attempt");
1326
- this.name = "AttemptToolCatalogStaleError";
1327
- }
1328
- };
1329
- var AttemptToolNotFoundError = class extends Error {
1330
- code = "tool_not_found";
1331
- constructor() {
1332
- super("Tool is not present in the active execution attempt catalog");
1333
- this.name = "AttemptToolNotFoundError";
1334
- }
1335
- };
1336
- var AttemptToolApprovalRequiredError = class extends Error {
1337
- code = "approval_required";
1338
- constructor() {
1339
- super("Tool requires human approval and must be invoked through the agent");
1340
- this.name = "AttemptToolApprovalRequiredError";
1341
- }
1342
- };
1343
- var AttemptToolCatalogIntegrityError = class extends Error {
1344
- code = "catalog_integrity_failed";
1345
- constructor() {
1346
- super("Attempt tool catalog digest does not match its authoritative content");
1347
- this.name = "AttemptToolCatalogIntegrityError";
1348
- }
1349
- };
1350
- var AttemptToolCatalogTooLargeError = class extends Error {
1351
- code = "catalog_too_large";
1352
- constructor() {
1353
- super("Attempt tool catalog exceeds the maximum serialized size");
1354
- this.name = "AttemptToolCatalogTooLargeError";
1355
- }
1356
- };
1357
- var AttemptToolInputValidationError = class extends Error {
1358
- code = "invalid_tool_arguments";
1359
- constructor() {
1360
- super("Tool arguments do not match the attempt catalog input schema");
1361
- this.name = "AttemptToolInputValidationError";
1362
- }
1363
- };
1364
- var AttemptToolOutputValidationError = class extends Error {
1365
- code = "invalid_tool_result";
1366
- constructor() {
1367
- super("Tool result does not match the attempt catalog output schema");
1368
- this.name = "AttemptToolOutputValidationError";
1369
- }
1370
- };
1371
1193
  var CodemodeTransportError = class extends Error {
1372
- constructor(message, status = null) {
1194
+ constructor(message, status = null, options = {}) {
1373
1195
  super(message);
1374
1196
  this.status = status;
1375
1197
  this.name = "CodemodeTransportError";
1198
+ this.remoteCode = options.code ?? null;
1199
+ this.retryable = options.retryable ?? null;
1200
+ this.outcomeUnknown = options.outcomeUnknown ?? null;
1201
+ this.details = options.details ?? null;
1376
1202
  }
1377
1203
  code = "codemode_transport_error";
1204
+ remoteCode;
1205
+ retryable;
1206
+ outcomeUnknown;
1207
+ details;
1378
1208
  };
1379
1209
  var CodemodeOperationError = class extends Error {
1380
1210
  constructor(operation, code) {
@@ -1433,15 +1263,22 @@ var CodemodeClient = class {
1433
1263
  return compileCodemodeTools(await this.catalog(options), this);
1434
1264
  }
1435
1265
  async call(identity, argumentsValue = {}, options = {}) {
1436
- const catalog = await this.catalog(options.signal ? { signal: options.signal } : {});
1437
- if (!catalog.entries.some(
1438
- (entry) => entry.identity.serverId === identity.serverId && entry.identity.toolName === identity.toolName
1439
- )) {
1440
- throw new AttemptToolNotFoundError();
1441
- }
1442
- const operationId = options.operationId ?? randomUUID();
1266
+ return (await this.callResolved(
1267
+ (catalog) => catalog.entries.find(
1268
+ (entry) => entry.identity.serverId === identity.serverId && entry.identity.toolName === identity.toolName
1269
+ ) ?? null,
1270
+ argumentsValue,
1271
+ options
1272
+ )).result;
1273
+ }
1274
+ async callResolved(resolveEntry, argumentsValue, options) {
1275
+ let catalog = await this.catalog(options.signal ? { signal: options.signal } : {});
1276
+ let entry = resolveEntry(catalog);
1277
+ if (!entry) throw new AttemptToolNotFoundError();
1278
+ const operationId = options.operationId ?? randomUUID2();
1443
1279
  const deadline = Date.now() + boundedPositiveInteger(options.timeoutMs ?? this.timeoutMs, 1e3, 60 * 6e4);
1444
1280
  let submitted = false;
1281
+ let staleRefreshAttempted = false;
1445
1282
  let operation = null;
1446
1283
  let nextNotifyAt = 0;
1447
1284
  while (true) {
@@ -1451,7 +1288,7 @@ var CodemodeClient = class {
1451
1288
  `Codemode operation ${operationId} did not settle before the client deadline`
1452
1289
  );
1453
1290
  }
1454
- const shouldNotify = !submitted || operation?.state === "queued" && Date.now() >= nextNotifyAt;
1291
+ const shouldNotify = !submitted || (operation?.state === "queued" || operation?.state === "running") && Date.now() >= nextNotifyAt;
1455
1292
  if (shouldNotify) {
1456
1293
  submitted = true;
1457
1294
  nextNotifyAt = Date.now() + 2e3;
@@ -1459,21 +1296,55 @@ var CodemodeClient = class {
1459
1296
  operation = await this.submit(
1460
1297
  operationId,
1461
1298
  catalog.digest,
1462
- identity,
1299
+ entry.identity,
1463
1300
  argumentsValue,
1464
1301
  options.signal
1465
1302
  );
1466
1303
  } catch (error) {
1304
+ if (operation === null && !staleRefreshAttempted && error instanceof CodemodeTransportError && error.remoteCode === "codemode_catalog_stale") {
1305
+ staleRefreshAttempted = true;
1306
+ catalog = await this.catalog({
1307
+ refresh: true,
1308
+ ...options.signal ? { signal: options.signal } : {}
1309
+ });
1310
+ entry = resolveEntry(catalog);
1311
+ if (!entry) throw new AttemptToolNotFoundError();
1312
+ submitted = false;
1313
+ nextNotifyAt = 0;
1314
+ continue;
1315
+ }
1316
+ if (options.signal?.aborted) throw error;
1317
+ if (operation === null && !canReconcileCodemodeSubmission(error)) throw error;
1318
+ let recovered;
1467
1319
  try {
1468
- operation = await this.read(operationId, options.signal);
1469
- } catch {
1470
- throw error;
1320
+ recovered = await this.read(operationId, options.signal);
1321
+ } catch (recoveryError) {
1322
+ if (options.signal?.aborted) throw recoveryError;
1323
+ throw new CodemodeTransportError(
1324
+ `Codemode operation ${operationId} could not be reconciled after its submission response failed`,
1325
+ null,
1326
+ {
1327
+ code: "codemode_operation_recovery_unavailable",
1328
+ retryable: true,
1329
+ outcomeUnknown: true,
1330
+ details: { operationId }
1331
+ }
1332
+ );
1471
1333
  }
1334
+ assertRecoveredCodemodeOperation(recovered, {
1335
+ operationId,
1336
+ catalog,
1337
+ identity: entry.identity,
1338
+ arguments: argumentsValue
1339
+ });
1340
+ operation = recovered;
1472
1341
  }
1473
1342
  } else {
1474
1343
  operation = await this.read(operationId, options.signal);
1475
1344
  }
1476
- if (operation.state === "completed") return AttemptToolResult.parse(operation.result);
1345
+ if (operation.state === "completed") {
1346
+ return { result: AttemptToolResult.parse(operation.result), entry };
1347
+ }
1477
1348
  if (["failed", "outcome_unknown", "cancelled"].includes(operation.state)) {
1478
1349
  throw new CodemodeOperationError(
1479
1350
  operation,
@@ -1488,25 +1359,22 @@ var CodemodeClient = class {
1488
1359
  if (path.length < 2 || path.some((segment) => segment.length === 0)) {
1489
1360
  throw new AttemptToolNotFoundError();
1490
1361
  }
1491
- const catalog = await this.catalog(options.signal ? { signal: options.signal } : {});
1492
- const matches = catalog.entries.filter(
1493
- (entry) => entry.codemodePath.length === path.length && entry.codemodePath.every((segment, index) => segment === path[index])
1494
- );
1495
- if (matches.length !== 1) throw new AttemptToolNotFoundError();
1496
- return await this.call(matches[0].identity, argumentsValue, options);
1362
+ return (await this.callResolved(
1363
+ (catalog) => catalogEntryForPath(catalog, path),
1364
+ argumentsValue,
1365
+ options
1366
+ )).result;
1497
1367
  }
1498
1368
  /** Return structured content when the catalog declares it; otherwise retain the full MCP result. */
1499
1369
  async callPathValue(path, argumentsValue = {}, options = {}) {
1500
1370
  if (path.length < 2 || path.some((segment) => segment.length === 0)) {
1501
1371
  throw new AttemptToolNotFoundError();
1502
1372
  }
1503
- const catalog = await this.catalog(options.signal ? { signal: options.signal } : {});
1504
- const matches = catalog.entries.filter(
1505
- (entry2) => entry2.codemodePath.length === path.length && entry2.codemodePath.every((segment, index) => segment === path[index])
1373
+ const { result, entry } = await this.callResolved(
1374
+ (catalog) => catalogEntryForPath(catalog, path),
1375
+ argumentsValue,
1376
+ options
1506
1377
  );
1507
- if (matches.length !== 1) throw new AttemptToolNotFoundError();
1508
- const entry = matches[0];
1509
- const result = await this.call(entry.identity, argumentsValue, options);
1510
1378
  if (!entry.outputSchema) return result;
1511
1379
  if (result.isError) throw new CodemodeToolCallError(result);
1512
1380
  if (!result.structuredContent) {
@@ -1543,22 +1411,46 @@ var CodemodeClient = class {
1543
1411
  ...init,
1544
1412
  headers: {
1545
1413
  ...Object.fromEntries(new Headers(init.headers).entries()),
1546
- [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
1547
1414
  authorization: `Bearer ${token}`
1548
1415
  }
1549
1416
  });
1550
1417
  if (!response.ok) {
1551
1418
  let message = `Codemode request failed with HTTP ${response.status}`;
1419
+ let errorOptions = {};
1552
1420
  try {
1553
- const payload = await response.json();
1554
- if (typeof payload.error?.message === "string") message = payload.error.message;
1421
+ const error = parseCodemodeApiError(await response.json());
1422
+ if (error?.message) message = error.message;
1423
+ if (error) {
1424
+ errorOptions = {
1425
+ ...error.code ? { code: error.code } : {},
1426
+ ...error.retryable === void 0 ? {} : { retryable: error.retryable },
1427
+ ...error.outcomeUnknown === void 0 ? {} : { outcomeUnknown: error.outcomeUnknown },
1428
+ ...error.details ? { details: error.details } : {}
1429
+ };
1430
+ }
1555
1431
  } catch {
1556
1432
  }
1557
- throw new CodemodeTransportError(message, response.status);
1433
+ throw new CodemodeTransportError(message, response.status, errorOptions);
1558
1434
  }
1559
1435
  return response;
1560
1436
  }
1561
1437
  };
1438
+ function canReconcileCodemodeSubmission(error) {
1439
+ return !(error instanceof CodemodeTransportError) || error.outcomeUnknown === true;
1440
+ }
1441
+ function assertRecoveredCodemodeOperation(operation, expected) {
1442
+ const matches = operation.operationId === expected.operationId && operation.accountId === expected.catalog.accountId && operation.workspaceId === expected.catalog.workspaceId && operation.sessionId === expected.catalog.sessionId && operation.turnId === expected.catalog.turnId && operation.attemptId === expected.catalog.attemptId && operation.executionGeneration === expected.catalog.executionGeneration && operation.catalogDigest === expected.catalog.digest && operation.identity.serverId === expected.identity.serverId && operation.identity.toolName === expected.identity.toolName && digestCanonicalJson(operation.arguments) === digestCanonicalJson(expected.arguments);
1443
+ if (matches) return;
1444
+ throw new CodemodeTransportError(
1445
+ "Codemode operation id is already bound to a different request",
1446
+ 409,
1447
+ {
1448
+ code: "codemode_operation_conflict",
1449
+ retryable: false,
1450
+ outcomeUnknown: false
1451
+ }
1452
+ );
1453
+ }
1562
1454
  function compileCodemodeTools(catalog, client) {
1563
1455
  const verified = parseVerifiedAttemptToolCatalog(catalog);
1564
1456
  const root = /* @__PURE__ */ Object.create(null);
@@ -1581,97 +1473,65 @@ function compileCodemodeTools(catalog, client) {
1581
1473
  return root;
1582
1474
  }
1583
1475
  var AttemptToolEnvironment = class {
1584
- constructor(catalog, definitions, authorize) {
1585
- this.authorize = authorize;
1476
+ constructor(catalog, gateway) {
1586
1477
  this.catalog = catalog;
1587
- for (const definition of definitions) {
1588
- this.byIdentity.set(identityKey(definition.entry.identity), definition);
1589
- this.byModelName.set(definition.entry.modelName, definition);
1590
- }
1478
+ this.gateway = gateway;
1591
1479
  }
1592
- catalog;
1593
- byIdentity = /* @__PURE__ */ new Map();
1594
- byModelName = /* @__PURE__ */ new Map();
1595
1480
  async call(input, context = {}) {
1481
+ return await (await this.prepareCall(input, context)).execute();
1482
+ }
1483
+ async prepareCall(input, context = {}) {
1596
1484
  const call = AttemptToolCall.parse(input);
1597
- if (call.catalogDigest !== this.catalog.digest) {
1598
- throw new AttemptToolCatalogStaleError();
1599
- }
1600
- const definition = this.byIdentity.get(identityKey(call.identity));
1601
- if (!definition) {
1602
- throw new AttemptToolNotFoundError();
1603
- }
1604
- if (call.caller.kind === "codemode" && definition.entry.approval === "human") {
1605
- throw new AttemptToolApprovalRequiredError();
1606
- }
1607
- if (!definition.validateInput(call.arguments)) {
1608
- throw new AttemptToolInputValidationError();
1609
- }
1610
- await this.authorize?.({ call, entry: definition.entry });
1611
- const result = AttemptToolResult.parse(
1612
- await definition.execute(call.arguments, {
1613
- operationId: call.operationId,
1614
- caller: call.caller,
1615
- ...context.transportMeta === void 0 ? {} : { transportMeta: context.transportMeta },
1616
- ...context.signal === void 0 ? {} : { signal: context.signal }
1617
- })
1618
- );
1619
- if (!result.isError && definition.validateOutput) {
1620
- const outputMatchesSchema = result.structuredContent !== void 0 && definition.validateOutput(result.structuredContent);
1621
- if (!outputMatchesSchema && !isToolResultSpilledReceipt(result.structuredContent)) {
1622
- throw new AttemptToolOutputValidationError();
1623
- }
1624
- }
1625
- return result;
1485
+ return await this.gateway.prepareCall(call, context);
1626
1486
  }
1627
1487
  async callModel(input) {
1628
- const definition = this.byModelName.get(input.modelName);
1629
- if (!definition) {
1630
- throw new AttemptToolNotFoundError();
1631
- }
1632
- const call = AttemptToolCall.parse({
1633
- operationId: input.operationId ?? randomUUID(),
1634
- catalogDigest: this.catalog.digest,
1635
- identity: definition.entry.identity,
1636
- arguments: input.arguments,
1637
- caller: { kind: "model", subjectId: input.subjectId }
1638
- });
1639
- return await this.call(call, {
1640
- ...input.transportMeta === void 0 ? {} : { transportMeta: input.transportMeta },
1641
- ...input.signal === void 0 ? {} : { signal: input.signal }
1642
- });
1488
+ return await this.gateway.callModel(input);
1643
1489
  }
1644
1490
  };
1645
1491
  function createAttemptToolEnvironment(input) {
1646
1492
  const createdAt = (input.createdAt ?? /* @__PURE__ */ new Date()).toISOString();
1647
- const paths = allocateCodemodePaths(input.definitions);
1648
- const schemaValidators = createSchemaValidators();
1649
- const compiled = input.definitions.map((definition, index) => {
1650
- const { execute, codemodePath: _path, ...entryInput } = definition;
1651
- const entry = AttemptToolCatalogEntry.parse({
1652
- ...entryInput,
1653
- codemodePath: paths[index]
1654
- });
1655
- return {
1656
- entry,
1657
- execute,
1658
- validateInput: compileCatalogSchema(schemaValidators, entry.inputSchema),
1659
- validateOutput: entry.outputSchema ? compileCatalogSchema(schemaValidators, entry.outputSchema) : null
1660
- };
1661
- });
1493
+ const prepared = prepareToolGatewayDefinitions(
1494
+ input.definitions.map(
1495
+ (definition) => ({
1496
+ ...definition,
1497
+ execute: async (argumentsValue, context) => await definition.execute(argumentsValue, {
1498
+ ...context,
1499
+ caller: AttemptToolCaller.parse(context.caller)
1500
+ })
1501
+ })
1502
+ )
1503
+ );
1662
1504
  const unsigned = {
1663
1505
  version: ATTEMPT_TOOL_CATALOG_VERSION,
1664
1506
  ...input.scope,
1665
1507
  generation: input.generation,
1666
1508
  createdAt,
1667
- entries: compiled.map(({ entry }) => entry)
1509
+ entries: [...prepared.entries]
1668
1510
  };
1669
1511
  const catalog = AttemptToolCatalog.parse({
1670
1512
  ...unsigned,
1671
1513
  digest: digestAttemptToolCatalog(unsigned)
1672
1514
  });
1673
1515
  assertCatalogSize(catalog);
1674
- return new AttemptToolEnvironment(catalog, compiled, input.authorize);
1516
+ return new AttemptToolEnvironment(
1517
+ catalog,
1518
+ prepared.create({
1519
+ catalogDigest: catalog.digest,
1520
+ requireApproval: (entry, caller) => caller.kind === "codemode" && entry.approval === "human",
1521
+ ...input.confirmModelApproval ? {
1522
+ confirmModelApproval: ({ entry, subjectId }) => input.confirmModelApproval({
1523
+ modelName: entry.modelName,
1524
+ subjectId
1525
+ })
1526
+ } : {},
1527
+ ...input.authorize ? {
1528
+ authorize: async ({ call, entry }) => await input.authorize({
1529
+ call: AttemptToolCall.parse(call),
1530
+ entry
1531
+ })
1532
+ } : {}
1533
+ })
1534
+ );
1675
1535
  }
1676
1536
  function digestAttemptToolCatalog(catalog) {
1677
1537
  const { createdAt: _createdAt, ...authoritative } = catalog;
@@ -1712,84 +1572,6 @@ function assertCatalogSize(catalog) {
1712
1572
  throw new AttemptToolCatalogTooLargeError();
1713
1573
  }
1714
1574
  }
1715
- var COMPILED_CATALOG_SCHEMA_CACHE_MAX_ENTRIES = 512;
1716
- var compiledCatalogSchemaCache = /* @__PURE__ */ new Map();
1717
- function createSchemaValidators() {
1718
- const options = {
1719
- allErrors: false,
1720
- coerceTypes: false,
1721
- strict: false,
1722
- useDefaults: false,
1723
- validateFormats: false
1724
- };
1725
- return {
1726
- draft7: new Ajv(options),
1727
- draft2019: new Ajv2019(options),
1728
- draft2020: new Ajv2020(options)
1729
- };
1730
- }
1731
- function compileCatalogSchema(validators, schema) {
1732
- const dialect = typeof schema.$schema === "string" ? schema.$schema : "";
1733
- const family = dialect.includes("2020-12") ? "2020-12" : dialect.includes("2019-09") ? "2019-09" : "draft7";
1734
- const cacheKey = `${family}:${digestCanonicalJson(schema)}`;
1735
- const cached = compiledCatalogSchemaCache.get(cacheKey);
1736
- if (cached) {
1737
- compiledCatalogSchemaCache.delete(cacheKey);
1738
- compiledCatalogSchemaCache.set(cacheKey, cached);
1739
- return cached;
1740
- }
1741
- const compiled = family === "2020-12" ? validators.draft2020.compile(schema) : family === "2019-09" ? validators.draft2019.compile(schema) : validators.draft7.compile(schema);
1742
- while (compiledCatalogSchemaCache.size >= COMPILED_CATALOG_SCHEMA_CACHE_MAX_ENTRIES) {
1743
- const oldest = compiledCatalogSchemaCache.keys().next().value;
1744
- if (oldest === void 0) break;
1745
- compiledCatalogSchemaCache.delete(oldest);
1746
- }
1747
- compiledCatalogSchemaCache.set(cacheKey, compiled);
1748
- return compiled;
1749
- }
1750
- function allocateCodemodePaths(definitions) {
1751
- const bases = definitions.map(
1752
- (definition) => (definition.codemodePath?.length ? definition.codemodePath : [definition.identity.serverId, definition.identity.toolName]).map(safeNamespaceSegment)
1753
- );
1754
- const counts = /* @__PURE__ */ new Map();
1755
- for (const path of bases) {
1756
- const key = path.join("\0");
1757
- counts.set(key, (counts.get(key) ?? 0) + 1);
1758
- }
1759
- return bases.map((base, index) => {
1760
- const key = base.join("\0");
1761
- if (counts.get(key) === 1) return base;
1762
- const suffix = `_${shortIdentityDigest(definitions[index].identity)}`;
1763
- const last = base.at(-1);
1764
- return [...base.slice(0, -1), `${last.slice(0, 128 - suffix.length)}${suffix}`];
1765
- });
1766
- }
1767
- function safeNamespaceSegment(value) {
1768
- let normalized = value.replace(/[^A-Za-z0-9_$]/gu, "_");
1769
- if (!/^[A-Za-z_$]/u.test(normalized)) normalized = `_${normalized}`;
1770
- if (["__proto__", "prototype", "constructor"].includes(normalized)) {
1771
- normalized = `_${normalized}`;
1772
- }
1773
- return normalized.slice(0, 128) || "_";
1774
- }
1775
- function shortIdentityDigest(identity) {
1776
- return createHash("sha256").update(identityKey(identity), "utf8").digest("hex").slice(0, 10);
1777
- }
1778
- function identityKey(identity) {
1779
- return `${identity.serverId}\0${identity.toolName}`;
1780
- }
1781
- function digestCanonicalJson(value) {
1782
- return createHash("sha256").update(JSON.stringify(canonicalJsonValue(value)), "utf8").digest("hex");
1783
- }
1784
- function canonicalJsonValue(value) {
1785
- if (Array.isArray(value)) return value.map(canonicalJsonValue);
1786
- if (value !== null && typeof value === "object") {
1787
- return Object.fromEntries(
1788
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalJsonValue(entry)])
1789
- );
1790
- }
1791
- return value;
1792
- }
1793
1575
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
1794
1576
  function boundedPositiveInteger(value, minimum, maximum) {
1795
1577
  if (!Number.isFinite(value)) return minimum;
@@ -1807,6 +1589,30 @@ function structuredToolError(result) {
1807
1589
  retryable: error?.retryable === true
1808
1590
  };
1809
1591
  }
1592
+ function catalogEntryForPath(catalog, path) {
1593
+ const matches = catalog.entries.filter(
1594
+ (entry) => entry.codemodePath.length === path.length && entry.codemodePath.every((segment, index) => segment === path[index])
1595
+ );
1596
+ return matches.length === 1 ? matches[0] : null;
1597
+ }
1598
+ function parseCodemodeApiError(input) {
1599
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
1600
+ const root = input;
1601
+ const nested = root.error && typeof root.error === "object" && !Array.isArray(root.error) ? root.error : root;
1602
+ const details = nested.details && typeof nested.details === "object" && !Array.isArray(nested.details) ? nested.details : void 0;
1603
+ const detailCode = details?.code;
1604
+ const code = typeof detailCode === "string" && /^[a-z0-9_]{1,128}$/u.test(detailCode) ? detailCode : void 0;
1605
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1606
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1607
+ const outcomeUnknown = typeof nested.outcomeUnknown === "boolean" ? nested.outcomeUnknown : void 0;
1608
+ return message || code || retryable !== void 0 || outcomeUnknown !== void 0 || details ? {
1609
+ ...message ? { message } : {},
1610
+ ...code ? { code } : {},
1611
+ ...retryable === void 0 ? {} : { retryable },
1612
+ ...outcomeUnknown === void 0 ? {} : { outcomeUnknown },
1613
+ ...details ? { details } : {}
1614
+ } : null;
1615
+ }
1810
1616
  async function abortableDelay(delayMs, signal) {
1811
1617
  if (!signal) {
1812
1618
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -1834,7 +1640,9 @@ export {
1834
1640
  AttemptToolInputValidationError,
1835
1641
  AttemptToolNotFoundError,
1836
1642
  AttemptToolOutputValidationError,
1643
+ AttemptToolPathCollisionError,
1837
1644
  CODEMODE_ENVIRONMENT,
1645
+ CODEMODE_SITE_LOCAL_PATH,
1838
1646
  CodemodeArtifact,
1839
1647
  CodemodeArtifactCollection,
1840
1648
  CodemodeArtifactExport,
@@ -1867,6 +1675,7 @@ export {
1867
1675
  codemodeDispatchSubject,
1868
1676
  compileCodemodeTools,
1869
1677
  createAttemptToolEnvironment,
1678
+ createCodemodeSiteRequestHandler,
1870
1679
  createCodemodeTools,
1871
1680
  createOpenGeniCodemode,
1872
1681
  decodeCodemodeDispatchAck,