@opengeni/codemode 0.4.25-canary.0 → 0.4.27-canary.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/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,160 @@ 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
1090
  }
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
- }
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 (pathname.startsWith(`${CODEMODE_SITE_LOCAL_PATH}/sdk/`)) {
1103
+ const path = pathname.slice(`${CODEMODE_SITE_LOCAL_PATH}/sdk`.length) + new URL(request.url).search;
1104
+ const response = await active.sessionRequest(path, {
1105
+ method: request.method,
1106
+ signal: request.signal,
1107
+ headers: {
1108
+ "content-type": request.headers.get("content-type") ?? "application/json",
1109
+ accept: request.headers.get("accept") ?? "application/json",
1110
+ ...request.headers.has("last-event-id") ? { "last-event-id": request.headers.get("last-event-id") } : {}
1111
+ },
1112
+ ...request.body ? { body: await request.text() } : {}
1113
+ });
1114
+ const headers = new Headers(response.headers);
1115
+ headers.delete("content-encoding");
1116
+ headers.delete("content-length");
1117
+ headers.delete("transfer-encoding");
1118
+ return new Response(response.body, {
1119
+ status: response.status,
1120
+ statusText: response.statusText,
1121
+ headers
1122
+ });
1123
+ }
1124
+ if (request.method === "GET" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/catalog`) {
1125
+ const catalog = await active.catalog({ signal: request.signal });
1126
+ return Response.json(projectSiteCatalog(catalog));
1127
+ }
1128
+ if (request.method === "GET" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/declarations`) {
1129
+ const catalog = await active.catalog({ signal: request.signal });
1130
+ return Response.json({
1131
+ catalogDigest: catalog.digest,
1132
+ moduleSpecifier: "@opengeni/sdk",
1133
+ source: generateToolDeclarations2(
1134
+ { digest: catalog.digest, entries: catalog.entries },
1135
+ {
1136
+ moduleSpecifier: "@opengeni/sdk",
1137
+ interfaceName: "OpenGeniGeneratedTools",
1138
+ callOptionsType: "OpenGeniToolCallOptions",
1139
+ fallbackResultType: "ToolGatewayResult",
1140
+ generatedBy: "@opengeni/codemode local Site preview",
1141
+ catalogDigestLabel: "Attempt catalog digest"
1142
+ }
1143
+ )
1144
+ });
1145
+ }
1146
+ if (request.method === "POST" && pathname === `${CODEMODE_SITE_LOCAL_PATH}/calls`) {
1147
+ const body = await request.json();
1148
+ if (!isSiteCall(body)) return siteError(400, "invalid_request", "Invalid Site tool call");
1149
+ const catalog = await active.catalog({ signal: request.signal });
1150
+ if (body.catalogDigest !== catalog.digest) {
1151
+ return siteError(409, "catalog_stale", "The local Site tool catalog changed", true);
1152
+ }
1153
+ const operationId = body.operationId ?? randomUUID();
1154
+ const result = await active.call(body.identity, body.arguments, {
1155
+ operationId,
1156
+ signal: request.signal
1157
+ });
1158
+ return Response.json({
1159
+ operationId,
1160
+ catalogDigest: catalog.digest,
1161
+ result
1162
+ });
1163
+ }
1164
+ return siteError(404, "not_found", "Local Site tool endpoint not found");
1165
+ } catch (error) {
1166
+ if (error instanceof CodemodeTransportError) {
1167
+ return siteError(
1168
+ error.status && error.status >= 400 && error.status <= 599 ? error.status : 502,
1169
+ error.remoteCode ?? error.code,
1170
+ error.message,
1171
+ error.retryable === true,
1172
+ error.outcomeUnknown === true
1173
+ );
1174
+ }
1175
+ return siteError(
1176
+ 500,
1177
+ "local_codemode_error",
1178
+ error instanceof Error ? error.message : "Local Site tool request failed"
1179
+ );
1263
1180
  }
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);
1181
+ };
1304
1182
  }
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)}...`;
1183
+ function projectSiteCatalog(catalog) {
1184
+ return {
1185
+ version: catalog.version,
1186
+ generation: catalog.generation,
1187
+ digest: catalog.digest,
1188
+ createdAt: catalog.createdAt,
1189
+ entries: catalog.entries
1190
+ };
1310
1191
  }
1311
- function renderDoc(value, indent) {
1312
- return [`${spaces(indent)}/** ${value} */`];
1192
+ function isSiteCall(value) {
1193
+ if (!isRecord(value) || typeof value.catalogDigest !== "string") return false;
1194
+ if (value.operationId !== void 0 && typeof value.operationId !== "string") return false;
1195
+ return isRecord(value.identity) && typeof value.identity.serverId === "string" && typeof value.identity.toolName === "string" && isRecord(value.arguments);
1313
1196
  }
1314
- function spaces(count) {
1315
- return " ".repeat(count);
1197
+ function siteError(status, code, message, retryable = false, outcomeUnknown = false) {
1198
+ return Response.json(
1199
+ {
1200
+ error: {
1201
+ code,
1202
+ message,
1203
+ retryable,
1204
+ ...outcomeUnknown ? { outcomeUnknown: true } : {}
1205
+ }
1206
+ },
1207
+ { status }
1208
+ );
1316
1209
  }
1317
- function isSchemaObject(value) {
1318
- return value !== null && typeof value === "object" && !Array.isArray(value);
1210
+ function isRecord(value) {
1211
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1319
1212
  }
1320
1213
 
1321
1214
  // 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
1215
  var CodemodeTransportError = class extends Error {
1372
- constructor(message, status = null) {
1216
+ constructor(message, status = null, options = {}) {
1373
1217
  super(message);
1374
1218
  this.status = status;
1375
1219
  this.name = "CodemodeTransportError";
1220
+ this.remoteCode = options.code ?? null;
1221
+ this.retryable = options.retryable ?? null;
1222
+ this.outcomeUnknown = options.outcomeUnknown ?? null;
1223
+ this.details = options.details ?? null;
1376
1224
  }
1377
1225
  code = "codemode_transport_error";
1226
+ remoteCode;
1227
+ retryable;
1228
+ outcomeUnknown;
1229
+ details;
1378
1230
  };
1379
1231
  var CodemodeOperationError = class extends Error {
1380
1232
  constructor(operation, code) {
@@ -1433,15 +1285,22 @@ var CodemodeClient = class {
1433
1285
  return compileCodemodeTools(await this.catalog(options), this);
1434
1286
  }
1435
1287
  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();
1288
+ return (await this.callResolved(
1289
+ (catalog) => catalog.entries.find(
1290
+ (entry) => entry.identity.serverId === identity.serverId && entry.identity.toolName === identity.toolName
1291
+ ) ?? null,
1292
+ argumentsValue,
1293
+ options
1294
+ )).result;
1295
+ }
1296
+ async callResolved(resolveEntry, argumentsValue, options) {
1297
+ let catalog = await this.catalog(options.signal ? { signal: options.signal } : {});
1298
+ let entry = resolveEntry(catalog);
1299
+ if (!entry) throw new AttemptToolNotFoundError();
1300
+ const operationId = options.operationId ?? randomUUID2();
1443
1301
  const deadline = Date.now() + boundedPositiveInteger(options.timeoutMs ?? this.timeoutMs, 1e3, 60 * 6e4);
1444
1302
  let submitted = false;
1303
+ let staleRefreshAttempted = false;
1445
1304
  let operation = null;
1446
1305
  let nextNotifyAt = 0;
1447
1306
  while (true) {
@@ -1451,7 +1310,7 @@ var CodemodeClient = class {
1451
1310
  `Codemode operation ${operationId} did not settle before the client deadline`
1452
1311
  );
1453
1312
  }
1454
- const shouldNotify = !submitted || operation?.state === "queued" && Date.now() >= nextNotifyAt;
1313
+ const shouldNotify = !submitted || (operation?.state === "queued" || operation?.state === "running") && Date.now() >= nextNotifyAt;
1455
1314
  if (shouldNotify) {
1456
1315
  submitted = true;
1457
1316
  nextNotifyAt = Date.now() + 2e3;
@@ -1459,21 +1318,55 @@ var CodemodeClient = class {
1459
1318
  operation = await this.submit(
1460
1319
  operationId,
1461
1320
  catalog.digest,
1462
- identity,
1321
+ entry.identity,
1463
1322
  argumentsValue,
1464
1323
  options.signal
1465
1324
  );
1466
1325
  } catch (error) {
1326
+ if (operation === null && !staleRefreshAttempted && error instanceof CodemodeTransportError && error.remoteCode === "codemode_catalog_stale") {
1327
+ staleRefreshAttempted = true;
1328
+ catalog = await this.catalog({
1329
+ refresh: true,
1330
+ ...options.signal ? { signal: options.signal } : {}
1331
+ });
1332
+ entry = resolveEntry(catalog);
1333
+ if (!entry) throw new AttemptToolNotFoundError();
1334
+ submitted = false;
1335
+ nextNotifyAt = 0;
1336
+ continue;
1337
+ }
1338
+ if (options.signal?.aborted) throw error;
1339
+ if (operation === null && !canReconcileCodemodeSubmission(error)) throw error;
1340
+ let recovered;
1467
1341
  try {
1468
- operation = await this.read(operationId, options.signal);
1469
- } catch {
1470
- throw error;
1342
+ recovered = await this.read(operationId, options.signal);
1343
+ } catch (recoveryError) {
1344
+ if (options.signal?.aborted) throw recoveryError;
1345
+ throw new CodemodeTransportError(
1346
+ `Codemode operation ${operationId} could not be reconciled after its submission response failed`,
1347
+ null,
1348
+ {
1349
+ code: "codemode_operation_recovery_unavailable",
1350
+ retryable: true,
1351
+ outcomeUnknown: true,
1352
+ details: { operationId }
1353
+ }
1354
+ );
1471
1355
  }
1356
+ assertRecoveredCodemodeOperation(recovered, {
1357
+ operationId,
1358
+ catalog,
1359
+ identity: entry.identity,
1360
+ arguments: argumentsValue
1361
+ });
1362
+ operation = recovered;
1472
1363
  }
1473
1364
  } else {
1474
1365
  operation = await this.read(operationId, options.signal);
1475
1366
  }
1476
- if (operation.state === "completed") return AttemptToolResult.parse(operation.result);
1367
+ if (operation.state === "completed") {
1368
+ return { result: AttemptToolResult.parse(operation.result), entry };
1369
+ }
1477
1370
  if (["failed", "outcome_unknown", "cancelled"].includes(operation.state)) {
1478
1371
  throw new CodemodeOperationError(
1479
1372
  operation,
@@ -1488,25 +1381,22 @@ var CodemodeClient = class {
1488
1381
  if (path.length < 2 || path.some((segment) => segment.length === 0)) {
1489
1382
  throw new AttemptToolNotFoundError();
1490
1383
  }
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);
1384
+ return (await this.callResolved(
1385
+ (catalog) => catalogEntryForPath(catalog, path),
1386
+ argumentsValue,
1387
+ options
1388
+ )).result;
1497
1389
  }
1498
1390
  /** Return structured content when the catalog declares it; otherwise retain the full MCP result. */
1499
1391
  async callPathValue(path, argumentsValue = {}, options = {}) {
1500
1392
  if (path.length < 2 || path.some((segment) => segment.length === 0)) {
1501
1393
  throw new AttemptToolNotFoundError();
1502
1394
  }
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])
1395
+ const { result, entry } = await this.callResolved(
1396
+ (catalog) => catalogEntryForPath(catalog, path),
1397
+ argumentsValue,
1398
+ options
1506
1399
  );
1507
- if (matches.length !== 1) throw new AttemptToolNotFoundError();
1508
- const entry = matches[0];
1509
- const result = await this.call(entry.identity, argumentsValue, options);
1510
1400
  if (!entry.outputSchema) return result;
1511
1401
  if (result.isError) throw new CodemodeToolCallError(result);
1512
1402
  if (!result.structuredContent) {
@@ -1537,28 +1427,59 @@ var CodemodeClient = class {
1537
1427
  });
1538
1428
  return CodemodeOperation.parse(await response.json());
1539
1429
  }
1540
- async request(path, init) {
1430
+ /** Server-side Site preview forwarding. The attempt bearer never enters the page. */
1431
+ async sessionRequest(path, init) {
1432
+ if (!path.startsWith("/v1/")) {
1433
+ throw new Error("Unsupported Site session API path");
1434
+ }
1435
+ return this.request(`/sdk${path}`, init, false);
1436
+ }
1437
+ async request(path, init, throwOnError = true) {
1541
1438
  const token = typeof this.options.token === "function" ? await this.options.token() : this.options.token;
1542
1439
  const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
1543
1440
  ...init,
1544
1441
  headers: {
1545
1442
  ...Object.fromEntries(new Headers(init.headers).entries()),
1546
- [OPENGENI_API_CONTRACT_HEADER]: OPENGENI_API_CONTRACT_REVISION,
1547
1443
  authorization: `Bearer ${token}`
1548
1444
  }
1549
1445
  });
1550
- if (!response.ok) {
1446
+ if (!response.ok && throwOnError) {
1551
1447
  let message = `Codemode request failed with HTTP ${response.status}`;
1448
+ let errorOptions = {};
1552
1449
  try {
1553
- const payload = await response.json();
1554
- if (typeof payload.error?.message === "string") message = payload.error.message;
1450
+ const error = parseCodemodeApiError(await response.json());
1451
+ if (error?.message) message = error.message;
1452
+ if (error) {
1453
+ errorOptions = {
1454
+ ...error.code ? { code: error.code } : {},
1455
+ ...error.retryable === void 0 ? {} : { retryable: error.retryable },
1456
+ ...error.outcomeUnknown === void 0 ? {} : { outcomeUnknown: error.outcomeUnknown },
1457
+ ...error.details ? { details: error.details } : {}
1458
+ };
1459
+ }
1555
1460
  } catch {
1556
1461
  }
1557
- throw new CodemodeTransportError(message, response.status);
1462
+ throw new CodemodeTransportError(message, response.status, errorOptions);
1558
1463
  }
1559
1464
  return response;
1560
1465
  }
1561
1466
  };
1467
+ function canReconcileCodemodeSubmission(error) {
1468
+ return !(error instanceof CodemodeTransportError) || error.outcomeUnknown === true;
1469
+ }
1470
+ function assertRecoveredCodemodeOperation(operation, expected) {
1471
+ 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);
1472
+ if (matches) return;
1473
+ throw new CodemodeTransportError(
1474
+ "Codemode operation id is already bound to a different request",
1475
+ 409,
1476
+ {
1477
+ code: "codemode_operation_conflict",
1478
+ retryable: false,
1479
+ outcomeUnknown: false
1480
+ }
1481
+ );
1482
+ }
1562
1483
  function compileCodemodeTools(catalog, client) {
1563
1484
  const verified = parseVerifiedAttemptToolCatalog(catalog);
1564
1485
  const root = /* @__PURE__ */ Object.create(null);
@@ -1581,97 +1502,65 @@ function compileCodemodeTools(catalog, client) {
1581
1502
  return root;
1582
1503
  }
1583
1504
  var AttemptToolEnvironment = class {
1584
- constructor(catalog, definitions, authorize) {
1585
- this.authorize = authorize;
1505
+ constructor(catalog, gateway) {
1586
1506
  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
- }
1507
+ this.gateway = gateway;
1591
1508
  }
1592
- catalog;
1593
- byIdentity = /* @__PURE__ */ new Map();
1594
- byModelName = /* @__PURE__ */ new Map();
1595
1509
  async call(input, context = {}) {
1510
+ return await (await this.prepareCall(input, context)).execute();
1511
+ }
1512
+ async prepareCall(input, context = {}) {
1596
1513
  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;
1514
+ return await this.gateway.prepareCall(call, context);
1626
1515
  }
1627
1516
  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
- });
1517
+ return await this.gateway.callModel(input);
1643
1518
  }
1644
1519
  };
1645
1520
  function createAttemptToolEnvironment(input) {
1646
1521
  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
- });
1522
+ const prepared = prepareToolGatewayDefinitions(
1523
+ input.definitions.map(
1524
+ (definition) => ({
1525
+ ...definition,
1526
+ execute: async (argumentsValue, context) => await definition.execute(argumentsValue, {
1527
+ ...context,
1528
+ caller: AttemptToolCaller.parse(context.caller)
1529
+ })
1530
+ })
1531
+ )
1532
+ );
1662
1533
  const unsigned = {
1663
1534
  version: ATTEMPT_TOOL_CATALOG_VERSION,
1664
1535
  ...input.scope,
1665
1536
  generation: input.generation,
1666
1537
  createdAt,
1667
- entries: compiled.map(({ entry }) => entry)
1538
+ entries: [...prepared.entries]
1668
1539
  };
1669
1540
  const catalog = AttemptToolCatalog.parse({
1670
1541
  ...unsigned,
1671
1542
  digest: digestAttemptToolCatalog(unsigned)
1672
1543
  });
1673
1544
  assertCatalogSize(catalog);
1674
- return new AttemptToolEnvironment(catalog, compiled, input.authorize);
1545
+ return new AttemptToolEnvironment(
1546
+ catalog,
1547
+ prepared.create({
1548
+ catalogDigest: catalog.digest,
1549
+ requireApproval: (entry, caller) => caller.kind === "codemode" && entry.approval === "human",
1550
+ ...input.confirmModelApproval ? {
1551
+ confirmModelApproval: ({ entry, subjectId }) => input.confirmModelApproval({
1552
+ modelName: entry.modelName,
1553
+ subjectId
1554
+ })
1555
+ } : {},
1556
+ ...input.authorize ? {
1557
+ authorize: async ({ call, entry }) => await input.authorize({
1558
+ call: AttemptToolCall.parse(call),
1559
+ entry
1560
+ })
1561
+ } : {}
1562
+ })
1563
+ );
1675
1564
  }
1676
1565
  function digestAttemptToolCatalog(catalog) {
1677
1566
  const { createdAt: _createdAt, ...authoritative } = catalog;
@@ -1712,84 +1601,6 @@ function assertCatalogSize(catalog) {
1712
1601
  throw new AttemptToolCatalogTooLargeError();
1713
1602
  }
1714
1603
  }
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
1604
  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
1605
  function boundedPositiveInteger(value, minimum, maximum) {
1795
1606
  if (!Number.isFinite(value)) return minimum;
@@ -1807,6 +1618,30 @@ function structuredToolError(result) {
1807
1618
  retryable: error?.retryable === true
1808
1619
  };
1809
1620
  }
1621
+ function catalogEntryForPath(catalog, path) {
1622
+ const matches = catalog.entries.filter(
1623
+ (entry) => entry.codemodePath.length === path.length && entry.codemodePath.every((segment, index) => segment === path[index])
1624
+ );
1625
+ return matches.length === 1 ? matches[0] : null;
1626
+ }
1627
+ function parseCodemodeApiError(input) {
1628
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
1629
+ const root = input;
1630
+ const nested = root.error && typeof root.error === "object" && !Array.isArray(root.error) ? root.error : root;
1631
+ const details = nested.details && typeof nested.details === "object" && !Array.isArray(nested.details) ? nested.details : void 0;
1632
+ const detailCode = details?.code;
1633
+ const code = typeof detailCode === "string" && /^[a-z0-9_]{1,128}$/u.test(detailCode) ? detailCode : void 0;
1634
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1635
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1636
+ const outcomeUnknown = typeof nested.outcomeUnknown === "boolean" ? nested.outcomeUnknown : void 0;
1637
+ return message || code || retryable !== void 0 || outcomeUnknown !== void 0 || details ? {
1638
+ ...message ? { message } : {},
1639
+ ...code ? { code } : {},
1640
+ ...retryable === void 0 ? {} : { retryable },
1641
+ ...outcomeUnknown === void 0 ? {} : { outcomeUnknown },
1642
+ ...details ? { details } : {}
1643
+ } : null;
1644
+ }
1810
1645
  async function abortableDelay(delayMs, signal) {
1811
1646
  if (!signal) {
1812
1647
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -1834,7 +1669,9 @@ export {
1834
1669
  AttemptToolInputValidationError,
1835
1670
  AttemptToolNotFoundError,
1836
1671
  AttemptToolOutputValidationError,
1672
+ AttemptToolPathCollisionError,
1837
1673
  CODEMODE_ENVIRONMENT,
1674
+ CODEMODE_SITE_LOCAL_PATH,
1838
1675
  CodemodeArtifact,
1839
1676
  CodemodeArtifactCollection,
1840
1677
  CodemodeArtifactExport,
@@ -1867,6 +1704,7 @@ export {
1867
1704
  codemodeDispatchSubject,
1868
1705
  compileCodemodeTools,
1869
1706
  createAttemptToolEnvironment,
1707
+ createCodemodeSiteRequestHandler,
1870
1708
  createCodemodeTools,
1871
1709
  createOpenGeniCodemode,
1872
1710
  decodeCodemodeDispatchAck,