@runtypelabs/sdk 4.15.0 → 4.17.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.cjs CHANGED
@@ -73,9 +73,15 @@ __export(index_exports, {
73
73
  STEP_TYPE_TO_METHOD: () => STEP_TYPE_TO_METHOD,
74
74
  SchedulesEndpoint: () => SchedulesEndpoint,
75
75
  SecretsEndpoint: () => SecretsEndpoint,
76
+ SkillDriftError: () => SkillDriftError,
77
+ SkillEnsureConflictError: () => SkillEnsureConflictError,
76
78
  SkillProposalsNamespace: () => SkillProposalsNamespace,
77
79
  SkillsNamespace: () => SkillsNamespace,
80
+ SurfaceDriftError: () => SurfaceDriftError,
81
+ SurfaceEnsureConflictError: () => SurfaceEnsureConflictError,
78
82
  SurfacesEndpoint: () => SurfacesEndpoint,
83
+ SurfacesNamespace: () => SurfacesNamespace,
84
+ ToolApprovalGrantsEndpoint: () => ToolApprovalGrantsEndpoint,
79
85
  ToolDriftError: () => ToolDriftError,
80
86
  ToolEnsureConflictError: () => ToolEnsureConflictError,
81
87
  ToolsEndpoint: () => ToolsEndpoint,
@@ -92,6 +98,8 @@ __export(index_exports, {
92
98
  computeAgentContentHash: () => computeAgentContentHash,
93
99
  computeFlowContentHash: () => computeFlowContentHash,
94
100
  computeProductContentHash: () => computeProductContentHash,
101
+ computeSkillContentHash: () => computeSkillContentHash,
102
+ computeSurfaceContentHash: () => computeSurfaceContentHash,
95
103
  computeToolContentHash: () => computeToolContentHash,
96
104
  createClient: () => createClient,
97
105
  createExternalTool: () => createExternalTool,
@@ -101,6 +109,8 @@ __export(index_exports, {
101
109
  defineFlow: () => defineFlow,
102
110
  definePlaybook: () => definePlaybook,
103
111
  defineProduct: () => defineProduct,
112
+ defineSkill: () => defineSkill,
113
+ defineSurface: () => defineSurface,
104
114
  defineTool: () => defineTool,
105
115
  deployWorkflow: () => deployWorkflow,
106
116
  ensureDefaultWorkflowHooks: () => ensureDefaultWorkflowHooks,
@@ -118,6 +128,8 @@ __export(index_exports, {
118
128
  normalizeAgentDefinition: () => normalizeAgentDefinition,
119
129
  normalizeCandidatePath: () => normalizeCandidatePath,
120
130
  normalizeProductDefinition: () => normalizeProductDefinition,
131
+ normalizeSkillDefinition: () => normalizeSkillDefinition,
132
+ normalizeSurfaceDefinition: () => normalizeSurfaceDefinition,
121
133
  normalizeToolDefinition: () => normalizeToolDefinition,
122
134
  parseFinalBuffer: () => parseFinalBuffer,
123
135
  parseLedgerArtifactRelativePath: () => parseLedgerArtifactRelativePath,
@@ -1231,20 +1243,20 @@ var FlowBuilder = class {
1231
1243
  */
1232
1244
  build() {
1233
1245
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
1234
- const request4 = { flow };
1246
+ const request6 = { flow };
1235
1247
  if (this.recordConfig) {
1236
- request4.record = this.recordConfig;
1248
+ request6.record = this.recordConfig;
1237
1249
  }
1238
1250
  if (this.messagesConfig) {
1239
- request4.messages = this.messagesConfig;
1251
+ request6.messages = this.messagesConfig;
1240
1252
  }
1241
1253
  if (this.inputsConfig) {
1242
- request4.inputs = this.inputsConfig;
1254
+ request6.inputs = this.inputsConfig;
1243
1255
  }
1244
1256
  if (Object.keys(this.optionsConfig).length > 0) {
1245
- request4.options = this.optionsConfig;
1257
+ request6.options = this.optionsConfig;
1246
1258
  }
1247
- return request4;
1259
+ return request6;
1248
1260
  }
1249
1261
  /**
1250
1262
  * Validate this prospective flow against the public validation endpoint
@@ -2638,15 +2650,15 @@ var RuntypeFlowBuilder = class {
2638
2650
  build() {
2639
2651
  const flowMode = this.mode === "existing" ? "existing" : this.mode;
2640
2652
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
2641
- const request4 = { flow };
2653
+ const request6 = { flow };
2642
2654
  if (this.recordConfig) {
2643
- request4.record = this.recordConfig;
2655
+ request6.record = this.recordConfig;
2644
2656
  }
2645
2657
  if (this.messagesConfig) {
2646
- request4.messages = this.messagesConfig;
2658
+ request6.messages = this.messagesConfig;
2647
2659
  }
2648
2660
  if (this.inputsConfig) {
2649
- request4.inputs = this.inputsConfig;
2661
+ request6.inputs = this.inputsConfig;
2650
2662
  }
2651
2663
  const options = {
2652
2664
  flowMode,
@@ -2664,8 +2676,8 @@ var RuntypeFlowBuilder = class {
2664
2676
  if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
2665
2677
  options.upsertOptions = this.upsertOptions;
2666
2678
  }
2667
- request4.options = options;
2668
- return request4;
2679
+ request6.options = options;
2680
+ return request6;
2669
2681
  }
2670
2682
  /**
2671
2683
  * Validate this prospective flow against the public validation endpoint
@@ -3259,6 +3271,192 @@ var PromptsNamespace = class {
3259
3271
  }
3260
3272
  };
3261
3273
 
3274
+ // src/skills-ensure.ts
3275
+ function isPlainObject2(value) {
3276
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3277
+ }
3278
+ function normalizeValue(value) {
3279
+ if (Array.isArray(value)) {
3280
+ return value.map((item) => normalizeValue(item));
3281
+ }
3282
+ if (isPlainObject2(value)) {
3283
+ const normalized = {};
3284
+ for (const key of Object.keys(value).sort()) {
3285
+ const entry = value[key];
3286
+ if (entry === void 0 || entry === null) continue;
3287
+ normalized[key] = normalizeValue(entry);
3288
+ }
3289
+ return normalized;
3290
+ }
3291
+ return value;
3292
+ }
3293
+ function normalizeSkillDefinition(definition) {
3294
+ const manifest = isPlainObject2(definition.manifest) ? definition.manifest : {};
3295
+ const rawFrontmatter = isPlainObject2(manifest.frontmatter) ? manifest.frontmatter : {};
3296
+ const frontmatterWithoutName = {};
3297
+ for (const key of Object.keys(rawFrontmatter)) {
3298
+ if (key === "name") continue;
3299
+ frontmatterWithoutName[key] = rawFrontmatter[key];
3300
+ }
3301
+ const frontmatter = normalizeValue(frontmatterWithoutName);
3302
+ const runtype = isPlainObject2(manifest.runtype) ? normalizeValue(manifest.runtype) : {};
3303
+ const body = typeof manifest.body === "string" ? manifest.body : "";
3304
+ return { frontmatter, runtype, body };
3305
+ }
3306
+ async function computeSkillContentHash(definition) {
3307
+ const serialized = JSON.stringify(normalizeSkillDefinition(definition));
3308
+ const encoded = new TextEncoder().encode(serialized);
3309
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3310
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3311
+ }
3312
+ var DEFINE_SKILL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "manifest"]);
3313
+ function defineSkill(input) {
3314
+ if (!input || typeof input !== "object") {
3315
+ throw new Error("defineSkill requires a definition object");
3316
+ }
3317
+ if (typeof input.name !== "string" || input.name.length === 0) {
3318
+ throw new Error('defineSkill requires a non-empty string "name"');
3319
+ }
3320
+ if (!isPlainObject2(input.manifest)) {
3321
+ throw new Error('defineSkill requires a "manifest" object ({ frontmatter, runtype, body })');
3322
+ }
3323
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SKILL_TOP_LEVEL_KEYS.has(key));
3324
+ if (unknownKeys.length > 0) {
3325
+ throw new Error(
3326
+ `defineSkill: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name and manifest.`
3327
+ );
3328
+ }
3329
+ const frontmatter = input.manifest.frontmatter;
3330
+ if (!isPlainObject2(frontmatter) || typeof frontmatter.name !== "string") {
3331
+ throw new Error("defineSkill: manifest.frontmatter.name is required");
3332
+ }
3333
+ if (frontmatter.name !== input.name) {
3334
+ throw new Error(
3335
+ `defineSkill: manifest.frontmatter.name ("${frontmatter.name}") must match the identity name ("${input.name}").`
3336
+ );
3337
+ }
3338
+ return { name: input.name, manifest: input.manifest };
3339
+ }
3340
+ var SkillEnsureConflictError = class extends Error {
3341
+ constructor(body) {
3342
+ super(body.error ?? `Skill ensure conflict: ${body.code}`);
3343
+ this.name = "SkillEnsureConflictError";
3344
+ this.code = body.code;
3345
+ this.lastModifiedSource = body.lastModifiedSource;
3346
+ this.modifiedAt = body.modifiedAt;
3347
+ this.currentHash = body.currentHash;
3348
+ }
3349
+ };
3350
+ var SkillDriftError = class extends Error {
3351
+ constructor(plan) {
3352
+ super(
3353
+ `Skill "${plan.skillId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.skills.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
3354
+ );
3355
+ this.name = "SkillDriftError";
3356
+ this.plan = plan;
3357
+ }
3358
+ };
3359
+ function parseRequestError2(err) {
3360
+ if (!(err instanceof Error)) return { status: null, body: null };
3361
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3362
+ if (!match) return { status: null, body: null };
3363
+ try {
3364
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3365
+ } catch {
3366
+ return { status: Number(match[1]), body: null };
3367
+ }
3368
+ }
3369
+ function toConflictError2(err) {
3370
+ const { status, body } = parseRequestError2(err);
3371
+ if (status !== 409 || !isPlainObject2(body)) return null;
3372
+ const code = body.code;
3373
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3374
+ return new SkillEnsureConflictError(
3375
+ body
3376
+ );
3377
+ }
3378
+ var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3379
+ function memoFor2(client) {
3380
+ let memo = serverHashMemo2.get(client);
3381
+ if (!memo) {
3382
+ memo = /* @__PURE__ */ new Map();
3383
+ serverHashMemo2.set(client, memo);
3384
+ }
3385
+ return memo;
3386
+ }
3387
+ function memoize2(memo, memoKey, result) {
3388
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3389
+ }
3390
+ async function request2(client, body) {
3391
+ try {
3392
+ return await client.post(
3393
+ "/skills/ensure",
3394
+ body
3395
+ );
3396
+ } catch (err) {
3397
+ const conflict = toConflictError2(err);
3398
+ if (conflict) throw conflict;
3399
+ throw err;
3400
+ }
3401
+ }
3402
+ async function ensureSkill(client, definition, options = {}) {
3403
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3404
+ const passthrough = {
3405
+ ...onConflict ? { onConflict } : {},
3406
+ ...release ? { release } : {},
3407
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3408
+ };
3409
+ if (dryRun || expectNoChanges) {
3410
+ const plan = await request2(client, {
3411
+ name: definition.name,
3412
+ definition: manifestToWire(definition),
3413
+ dryRun: true,
3414
+ ...passthrough
3415
+ });
3416
+ if (plan.result !== "plan") {
3417
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3418
+ }
3419
+ if (expectNoChanges && plan.changes !== "none") {
3420
+ throw new SkillDriftError(plan);
3421
+ }
3422
+ return plan;
3423
+ }
3424
+ const memo = memoFor2(client);
3425
+ const localHash = await computeSkillContentHash(definition);
3426
+ const memoKey = `${definition.name} ${localHash}`;
3427
+ const contentHash = memo.get(memoKey) ?? localHash;
3428
+ const probe = await request2(client, {
3429
+ name: definition.name,
3430
+ contentHash,
3431
+ ...passthrough
3432
+ });
3433
+ if (probe.result !== "definitionRequired") {
3434
+ memoize2(memo, memoKey, probe);
3435
+ return probe;
3436
+ }
3437
+ const converged = await request2(client, {
3438
+ name: definition.name,
3439
+ definition: manifestToWire(definition),
3440
+ ...passthrough
3441
+ });
3442
+ if (converged.result === "definitionRequired") {
3443
+ throw new Error("Server reported definitionRequired for a full-definition request");
3444
+ }
3445
+ memoize2(memo, memoKey, converged);
3446
+ return converged;
3447
+ }
3448
+ function manifestToWire(definition) {
3449
+ const manifest = definition.manifest;
3450
+ const frontmatter = { ...manifest.frontmatter };
3451
+ if (manifest.runtype && Object.keys(manifest.runtype).length > 0) {
3452
+ frontmatter.runtype = manifest.runtype;
3453
+ }
3454
+ return { frontmatter, body: manifest.body ?? "" };
3455
+ }
3456
+ async function pullSkill(client, name) {
3457
+ return client.get("/skills/pull", { name });
3458
+ }
3459
+
3262
3460
  // src/skills-namespace.ts
3263
3461
  var SkillProposalsNamespace = class {
3264
3462
  constructor(getClient) {
@@ -3399,6 +3597,22 @@ var SkillsNamespace = class {
3399
3597
  const client = this.getClient();
3400
3598
  await client.post(`/skills/${skillId}/versions/${versionId}/publish`);
3401
3599
  }
3600
+ /**
3601
+ * Statically scan a SKILL.md document for malicious patterns and return a
3602
+ * two-tier verdict — `warning` when a skill appears suspicious (low–medium
3603
+ * confidence), `error` when high-confidence malicious. Does not persist or
3604
+ * gate; use it as a "scan before save" affordance.
3605
+ *
3606
+ * @example
3607
+ * ```typescript
3608
+ * const { verdict } = await Runtype.skills.scan(skillMarkdown)
3609
+ * if (verdict.tier === 'error') console.warn(verdict.summary)
3610
+ * ```
3611
+ */
3612
+ async scan(markdown) {
3613
+ const client = this.getClient();
3614
+ return client.post("/skills/scan", { markdown });
3615
+ }
3402
3616
  /**
3403
3617
  * Import a single SKILL.md document. The imported skill lands with
3404
3618
  * `trustLevel: 'imported'` and a draft version.
@@ -3441,6 +3655,40 @@ var SkillsNamespace = class {
3441
3655
  const res = await client.get("/skills/bindings", { agentId });
3442
3656
  return res.data;
3443
3657
  }
3658
+ /**
3659
+ * Idempotently converge a `defineSkill` definition onto the platform.
3660
+ * Hash-first: the steady state is one tiny probe request. Creates or appends a
3661
+ * new version; never deletes. Identity is name + account scope. Pass
3662
+ * `release: 'publish'` to publish the converged version.
3663
+ *
3664
+ * @example
3665
+ * ```typescript
3666
+ * const reviewer = defineSkill({
3667
+ * name: 'code_reviewer',
3668
+ * manifest: {
3669
+ * frontmatter: { name: 'code_reviewer', description: 'Reviews pull requests' },
3670
+ * runtype: { trustLevel: 'org' },
3671
+ * body: '# Code Reviewer\n\nReview the diff...',
3672
+ * },
3673
+ * })
3674
+ *
3675
+ * // Converge + publish (CI/deploy).
3676
+ * const result = await Runtype.skills.ensure(reviewer, { release: 'publish' })
3677
+ *
3678
+ * // PR drift gate.
3679
+ * await Runtype.skills.ensure(reviewer, { expectNoChanges: true })
3680
+ * ```
3681
+ */
3682
+ async ensure(definition, options = {}) {
3683
+ return ensureSkill(this.getClient(), definition, options);
3684
+ }
3685
+ /**
3686
+ * Pull the canonical definition + provenance for a skill by name — the
3687
+ * absorb-drift direction of the ensure protocol.
3688
+ */
3689
+ async pull(name) {
3690
+ return pullSkill(this.getClient(), name);
3691
+ }
3444
3692
  };
3445
3693
 
3446
3694
  // src/agents-namespace.ts
@@ -3466,19 +3714,19 @@ var AGENT_CONFIG_KEYS = [
3466
3714
  "memory"
3467
3715
  ];
3468
3716
  var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
3469
- function isPlainObject2(value) {
3717
+ function isPlainObject3(value) {
3470
3718
  return value !== null && typeof value === "object" && !Array.isArray(value);
3471
3719
  }
3472
- function normalizeValue(value) {
3720
+ function normalizeValue2(value) {
3473
3721
  if (Array.isArray(value)) {
3474
- return value.map((item) => normalizeValue(item));
3722
+ return value.map((item) => normalizeValue2(item));
3475
3723
  }
3476
- if (isPlainObject2(value)) {
3724
+ if (isPlainObject3(value)) {
3477
3725
  const normalized = {};
3478
3726
  for (const key of Object.keys(value).sort()) {
3479
3727
  const entry = value[key];
3480
3728
  if (entry === void 0 || entry === null) continue;
3481
- normalized[key] = normalizeValue(entry);
3729
+ normalized[key] = normalizeValue2(entry);
3482
3730
  }
3483
3731
  return normalized;
3484
3732
  }
@@ -3486,11 +3734,11 @@ function normalizeValue(value) {
3486
3734
  }
3487
3735
  function normalizeAgentDefinition(definition) {
3488
3736
  const config = {};
3489
- const rawConfig = isPlainObject2(definition.config) ? definition.config : {};
3737
+ const rawConfig = isPlainObject3(definition.config) ? definition.config : {};
3490
3738
  for (const key of AGENT_CONFIG_KEY_LIST) {
3491
3739
  const value = rawConfig[key];
3492
3740
  if (value === void 0 || value === null) continue;
3493
- config[key] = normalizeValue(value);
3741
+ config[key] = normalizeValue2(value);
3494
3742
  }
3495
3743
  return {
3496
3744
  name: definition.name,
@@ -3508,7 +3756,7 @@ async function computeAgentContentHash(definition) {
3508
3756
  var DEFINE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "description", "icon", ...AGENT_CONFIG_KEYS]);
3509
3757
  function collectNonPortableToolRefs(config) {
3510
3758
  const tools = config.tools;
3511
- if (!isPlainObject2(tools)) return [];
3759
+ if (!isPlainObject3(tools)) return [];
3512
3760
  const found = [];
3513
3761
  const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
3514
3762
  const scanArray = (value, path) => {
@@ -3518,7 +3766,7 @@ function collectNonPortableToolRefs(config) {
3518
3766
  });
3519
3767
  };
3520
3768
  const scanKeys = (value, path) => {
3521
- if (!isPlainObject2(value)) return;
3769
+ if (!isPlainObject3(value)) return;
3522
3770
  for (const key of Object.keys(value)) {
3523
3771
  if (isAccountScoped(key)) found.push(`${path}.${key}`);
3524
3772
  }
@@ -3526,16 +3774,16 @@ function collectNonPortableToolRefs(config) {
3526
3774
  scanArray(tools.toolIds, "tools.toolIds");
3527
3775
  scanKeys(tools.toolConfigs, "tools.toolConfigs");
3528
3776
  scanKeys(tools.perToolLimits, "tools.perToolLimits");
3529
- if (isPlainObject2(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3530
- if (isPlainObject2(tools.subagentConfig)) {
3777
+ if (isPlainObject3(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3778
+ if (isPlainObject3(tools.subagentConfig)) {
3531
3779
  scanArray(tools.subagentConfig.toolPool, "tools.subagentConfig.toolPool");
3532
3780
  }
3533
- if (isPlainObject2(tools.codeModeConfig)) {
3781
+ if (isPlainObject3(tools.codeModeConfig)) {
3534
3782
  scanArray(tools.codeModeConfig.toolPool, "tools.codeModeConfig.toolPool");
3535
3783
  }
3536
3784
  if (Array.isArray(tools.runtimeTools)) {
3537
3785
  tools.runtimeTools.forEach((runtimeTool, i) => {
3538
- if (!isPlainObject2(runtimeTool) || !isPlainObject2(runtimeTool.config)) return;
3786
+ if (!isPlainObject3(runtimeTool) || !isPlainObject3(runtimeTool.config)) return;
3539
3787
  const base = `tools.runtimeTools[${i}].config`;
3540
3788
  const rtConfig = runtimeTool.config;
3541
3789
  if (runtimeTool.toolType === "subagent" && typeof rtConfig.agentId === "string" && rtConfig.agentId.startsWith("agent_")) {
@@ -3597,7 +3845,7 @@ var AgentDriftError = class extends Error {
3597
3845
  this.plan = plan;
3598
3846
  }
3599
3847
  };
3600
- function parseRequestError2(err) {
3848
+ function parseRequestError3(err) {
3601
3849
  if (!(err instanceof Error)) return { status: null, body: null };
3602
3850
  const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3603
3851
  if (!match) return { status: null, body: null };
@@ -3607,21 +3855,21 @@ function parseRequestError2(err) {
3607
3855
  return { status: Number(match[1]), body: null };
3608
3856
  }
3609
3857
  }
3610
- function toConflictError2(err) {
3611
- const { status, body } = parseRequestError2(err);
3612
- if (status !== 409 || !isPlainObject2(body)) return null;
3858
+ function toConflictError3(err) {
3859
+ const { status, body } = parseRequestError3(err);
3860
+ if (status !== 409 || !isPlainObject3(body)) return null;
3613
3861
  const code = body.code;
3614
3862
  if (code !== "external_modification" && code !== "remote_changed") return null;
3615
3863
  return new AgentEnsureConflictError(
3616
3864
  body
3617
3865
  );
3618
3866
  }
3619
- var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3620
- function memoFor2(client) {
3621
- let memo = serverHashMemo2.get(client);
3867
+ var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
3868
+ function memoFor3(client) {
3869
+ let memo = serverHashMemo3.get(client);
3622
3870
  if (!memo) {
3623
3871
  memo = /* @__PURE__ */ new Map();
3624
- serverHashMemo2.set(client, memo);
3872
+ serverHashMemo3.set(client, memo);
3625
3873
  }
3626
3874
  return memo;
3627
3875
  }
@@ -3658,7 +3906,7 @@ var AgentsNamespace = class {
3658
3906
  }
3659
3907
  return plan;
3660
3908
  }
3661
- const memo = memoFor2(client);
3909
+ const memo = memoFor3(client);
3662
3910
  const localHash = await computeAgentContentHash({
3663
3911
  ...definition,
3664
3912
  config: definition.config
@@ -3703,7 +3951,7 @@ var AgentsNamespace = class {
3703
3951
  body
3704
3952
  );
3705
3953
  } catch (err) {
3706
- const conflict = toConflictError2(err);
3954
+ const conflict = toConflictError3(err);
3707
3955
  if (conflict) throw conflict;
3708
3956
  throw err;
3709
3957
  }
@@ -3711,27 +3959,27 @@ var AgentsNamespace = class {
3711
3959
  };
3712
3960
 
3713
3961
  // src/tools-ensure.ts
3714
- function isPlainObject3(value) {
3962
+ function isPlainObject4(value) {
3715
3963
  return value !== null && typeof value === "object" && !Array.isArray(value);
3716
3964
  }
3717
- function normalizeValue2(value) {
3965
+ function normalizeValue3(value) {
3718
3966
  if (Array.isArray(value)) {
3719
- return value.map((item) => normalizeValue2(item));
3967
+ return value.map((item) => normalizeValue3(item));
3720
3968
  }
3721
- if (isPlainObject3(value)) {
3969
+ if (isPlainObject4(value)) {
3722
3970
  const normalized = {};
3723
3971
  for (const key of Object.keys(value).sort()) {
3724
3972
  const entry = value[key];
3725
3973
  if (entry === void 0 || entry === null) continue;
3726
- normalized[key] = normalizeValue2(entry);
3974
+ normalized[key] = normalizeValue3(entry);
3727
3975
  }
3728
3976
  return normalized;
3729
3977
  }
3730
3978
  return value;
3731
3979
  }
3732
3980
  function normalizeToolDefinition(definition) {
3733
- const parametersSchema = isPlainObject3(definition.parametersSchema) ? normalizeValue2(definition.parametersSchema) : {};
3734
- const config = isPlainObject3(definition.config) ? normalizeValue2(definition.config) : {};
3981
+ const parametersSchema = isPlainObject4(definition.parametersSchema) ? normalizeValue3(definition.parametersSchema) : {};
3982
+ const config = isPlainObject4(definition.config) ? normalizeValue3(definition.config) : {};
3735
3983
  return {
3736
3984
  toolType: definition.toolType,
3737
3985
  ...definition.description ? { description: definition.description } : {},
@@ -3776,10 +4024,10 @@ function defineTool(input) {
3776
4024
  `defineTool requires "toolType" to be one of: ${[...TOOL_DEFINITION_TYPES].join(", ")}`
3777
4025
  );
3778
4026
  }
3779
- if (!isPlainObject3(input.parametersSchema)) {
4027
+ if (!isPlainObject4(input.parametersSchema)) {
3780
4028
  throw new Error('defineTool requires a "parametersSchema" object (a JSON Schema)');
3781
4029
  }
3782
- if (!isPlainObject3(input.config)) {
4030
+ if (!isPlainObject4(input.config)) {
3783
4031
  throw new Error('defineTool requires a "config" object');
3784
4032
  }
3785
4033
  const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOOL_TOP_LEVEL_KEYS.has(key));
@@ -3815,7 +4063,7 @@ var ToolDriftError = class extends Error {
3815
4063
  this.plan = plan;
3816
4064
  }
3817
4065
  };
3818
- function parseRequestError3(err) {
4066
+ function parseRequestError4(err) {
3819
4067
  if (!(err instanceof Error)) return { status: null, body: null };
3820
4068
  const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3821
4069
  if (!match) return { status: null, body: null };
@@ -3825,35 +4073,35 @@ function parseRequestError3(err) {
3825
4073
  return { status: Number(match[1]), body: null };
3826
4074
  }
3827
4075
  }
3828
- function toConflictError3(err) {
3829
- const { status, body } = parseRequestError3(err);
3830
- if (status !== 409 || !isPlainObject3(body)) return null;
4076
+ function toConflictError4(err) {
4077
+ const { status, body } = parseRequestError4(err);
4078
+ if (status !== 409 || !isPlainObject4(body)) return null;
3831
4079
  const code = body.code;
3832
4080
  if (code !== "external_modification" && code !== "remote_changed") return null;
3833
4081
  return new ToolEnsureConflictError(
3834
4082
  body
3835
4083
  );
3836
4084
  }
3837
- var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
3838
- function memoFor3(client) {
3839
- let memo = serverHashMemo3.get(client);
4085
+ var serverHashMemo4 = /* @__PURE__ */ new WeakMap();
4086
+ function memoFor4(client) {
4087
+ let memo = serverHashMemo4.get(client);
3840
4088
  if (!memo) {
3841
4089
  memo = /* @__PURE__ */ new Map();
3842
- serverHashMemo3.set(client, memo);
4090
+ serverHashMemo4.set(client, memo);
3843
4091
  }
3844
4092
  return memo;
3845
4093
  }
3846
- function memoize2(memo, memoKey, result) {
4094
+ function memoize3(memo, memoKey, result) {
3847
4095
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3848
4096
  }
3849
- async function request2(client, body) {
4097
+ async function request3(client, body) {
3850
4098
  try {
3851
4099
  return await client.post(
3852
4100
  "/tools/ensure",
3853
4101
  body
3854
4102
  );
3855
4103
  } catch (err) {
3856
- const conflict = toConflictError3(err);
4104
+ const conflict = toConflictError4(err);
3857
4105
  if (conflict) throw conflict;
3858
4106
  throw err;
3859
4107
  }
@@ -3865,7 +4113,7 @@ async function ensureTool(client, definition, options = {}) {
3865
4113
  ...expectedRemoteHash ? { expectedRemoteHash } : {}
3866
4114
  };
3867
4115
  if (dryRun || expectNoChanges) {
3868
- const plan = await request2(client, {
4116
+ const plan = await request3(client, {
3869
4117
  name: definition.name,
3870
4118
  definition,
3871
4119
  dryRun: true,
@@ -3879,20 +4127,20 @@ async function ensureTool(client, definition, options = {}) {
3879
4127
  }
3880
4128
  return plan;
3881
4129
  }
3882
- const memo = memoFor3(client);
4130
+ const memo = memoFor4(client);
3883
4131
  const localHash = await computeToolContentHash(definition);
3884
4132
  const memoKey = `${definition.name} ${localHash}`;
3885
4133
  const contentHash = memo.get(memoKey) ?? localHash;
3886
- const probe = await request2(client, {
4134
+ const probe = await request3(client, {
3887
4135
  name: definition.name,
3888
4136
  contentHash,
3889
4137
  ...passthrough
3890
4138
  });
3891
4139
  if (probe.result !== "definitionRequired") {
3892
- memoize2(memo, memoKey, probe);
4140
+ memoize3(memo, memoKey, probe);
3893
4141
  return probe;
3894
4142
  }
3895
- const converged = await request2(client, {
4143
+ const converged = await request3(client, {
3896
4144
  name: definition.name,
3897
4145
  definition,
3898
4146
  ...passthrough
@@ -3900,7 +4148,7 @@ async function ensureTool(client, definition, options = {}) {
3900
4148
  if (converged.result === "definitionRequired") {
3901
4149
  throw new Error("Server reported definitionRequired for a full-definition request");
3902
4150
  }
3903
- memoize2(memo, memoKey, converged);
4151
+ memoize3(memo, memoKey, converged);
3904
4152
  return converged;
3905
4153
  }
3906
4154
  async function pullTool(client, name) {
@@ -3947,26 +4195,26 @@ var ToolsNamespace = class {
3947
4195
  };
3948
4196
 
3949
4197
  // src/products-ensure.ts
3950
- function isPlainObject4(value) {
4198
+ function isPlainObject5(value) {
3951
4199
  return value !== null && typeof value === "object" && !Array.isArray(value);
3952
4200
  }
3953
- function normalizeValue3(value) {
4201
+ function normalizeValue4(value) {
3954
4202
  if (Array.isArray(value)) {
3955
- return value.map((item) => normalizeValue3(item));
4203
+ return value.map((item) => normalizeValue4(item));
3956
4204
  }
3957
- if (isPlainObject4(value)) {
4205
+ if (isPlainObject5(value)) {
3958
4206
  const normalized = {};
3959
4207
  for (const key of Object.keys(value).sort()) {
3960
4208
  const entry = value[key];
3961
4209
  if (entry === void 0 || entry === null) continue;
3962
- normalized[key] = normalizeValue3(entry);
4210
+ normalized[key] = normalizeValue4(entry);
3963
4211
  }
3964
4212
  return normalized;
3965
4213
  }
3966
4214
  return value;
3967
4215
  }
3968
4216
  function normalizeProductDefinition(definition) {
3969
- const spec = isPlainObject4(definition.spec) ? normalizeValue3(definition.spec) : {};
4217
+ const spec = isPlainObject5(definition.spec) ? normalizeValue4(definition.spec) : {};
3970
4218
  return {
3971
4219
  ...definition.description ? { description: definition.description } : {},
3972
4220
  ...definition.icon ? { icon: definition.icon } : {},
@@ -3993,7 +4241,7 @@ function defineProduct(input) {
3993
4241
  if (input.icon != null && typeof input.icon !== "string") {
3994
4242
  throw new Error('defineProduct "icon" must be a string when provided');
3995
4243
  }
3996
- if (input.spec != null && !isPlainObject4(input.spec)) {
4244
+ if (input.spec != null && !isPlainObject5(input.spec)) {
3997
4245
  throw new Error('defineProduct "spec" must be an object when provided');
3998
4246
  }
3999
4247
  const unknownKeys = Object.keys(input).filter((key) => !DEFINE_PRODUCT_TOP_LEVEL_KEYS.has(key));
@@ -4028,7 +4276,7 @@ var ProductDriftError = class extends Error {
4028
4276
  this.plan = plan;
4029
4277
  }
4030
4278
  };
4031
- function parseRequestError4(err) {
4279
+ function parseRequestError5(err) {
4032
4280
  if (!(err instanceof Error)) return { status: null, body: null };
4033
4281
  const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
4034
4282
  if (!match) return { status: null, body: null };
@@ -4038,35 +4286,35 @@ function parseRequestError4(err) {
4038
4286
  return { status: Number(match[1]), body: null };
4039
4287
  }
4040
4288
  }
4041
- function toConflictError4(err) {
4042
- const { status, body } = parseRequestError4(err);
4043
- if (status !== 409 || !isPlainObject4(body)) return null;
4289
+ function toConflictError5(err) {
4290
+ const { status, body } = parseRequestError5(err);
4291
+ if (status !== 409 || !isPlainObject5(body)) return null;
4044
4292
  const code = body.code;
4045
4293
  if (code !== "external_modification" && code !== "remote_changed") return null;
4046
4294
  return new ProductEnsureConflictError(
4047
4295
  body
4048
4296
  );
4049
4297
  }
4050
- var serverHashMemo4 = /* @__PURE__ */ new WeakMap();
4051
- function memoFor4(client) {
4052
- let memo = serverHashMemo4.get(client);
4298
+ var serverHashMemo5 = /* @__PURE__ */ new WeakMap();
4299
+ function memoFor5(client) {
4300
+ let memo = serverHashMemo5.get(client);
4053
4301
  if (!memo) {
4054
4302
  memo = /* @__PURE__ */ new Map();
4055
- serverHashMemo4.set(client, memo);
4303
+ serverHashMemo5.set(client, memo);
4056
4304
  }
4057
4305
  return memo;
4058
4306
  }
4059
- function memoize3(memo, memoKey, result) {
4307
+ function memoize4(memo, memoKey, result) {
4060
4308
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
4061
4309
  }
4062
- async function request3(client, body) {
4310
+ async function request4(client, body) {
4063
4311
  try {
4064
4312
  return await client.post(
4065
4313
  "/products/ensure",
4066
4314
  body
4067
4315
  );
4068
4316
  } catch (err) {
4069
- const conflict = toConflictError4(err);
4317
+ const conflict = toConflictError5(err);
4070
4318
  if (conflict) throw conflict;
4071
4319
  throw err;
4072
4320
  }
@@ -4078,7 +4326,7 @@ async function ensureProduct(client, definition, options = {}) {
4078
4326
  ...expectedRemoteHash ? { expectedRemoteHash } : {}
4079
4327
  };
4080
4328
  if (dryRun || expectNoChanges) {
4081
- const plan = await request3(client, {
4329
+ const plan = await request4(client, {
4082
4330
  name: definition.name,
4083
4331
  definition,
4084
4332
  dryRun: true,
@@ -4092,20 +4340,20 @@ async function ensureProduct(client, definition, options = {}) {
4092
4340
  }
4093
4341
  return plan;
4094
4342
  }
4095
- const memo = memoFor4(client);
4343
+ const memo = memoFor5(client);
4096
4344
  const localHash = await computeProductContentHash(definition);
4097
4345
  const memoKey = `${definition.name} ${localHash}`;
4098
4346
  const contentHash = memo.get(memoKey) ?? localHash;
4099
- const probe = await request3(client, {
4347
+ const probe = await request4(client, {
4100
4348
  name: definition.name,
4101
4349
  contentHash,
4102
4350
  ...passthrough
4103
4351
  });
4104
4352
  if (probe.result !== "definitionRequired") {
4105
- memoize3(memo, memoKey, probe);
4353
+ memoize4(memo, memoKey, probe);
4106
4354
  return probe;
4107
4355
  }
4108
- const converged = await request3(client, {
4356
+ const converged = await request4(client, {
4109
4357
  name: definition.name,
4110
4358
  definition,
4111
4359
  ...passthrough
@@ -4113,7 +4361,7 @@ async function ensureProduct(client, definition, options = {}) {
4113
4361
  if (converged.result === "definitionRequired") {
4114
4362
  throw new Error("Server reported definitionRequired for a full-definition request");
4115
4363
  }
4116
- memoize3(memo, memoKey, converged);
4364
+ memoize4(memo, memoKey, converged);
4117
4365
  return converged;
4118
4366
  }
4119
4367
  async function pullProduct(client, name) {
@@ -4159,6 +4407,262 @@ var ProductsNamespace = class {
4159
4407
  }
4160
4408
  };
4161
4409
 
4410
+ // src/surfaces-ensure.ts
4411
+ function isPlainObject6(value) {
4412
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4413
+ }
4414
+ function normalizeValue5(value) {
4415
+ if (Array.isArray(value)) {
4416
+ return value.map((item) => normalizeValue5(item));
4417
+ }
4418
+ if (isPlainObject6(value)) {
4419
+ const normalized = {};
4420
+ for (const key of Object.keys(value).sort()) {
4421
+ const entry = value[key];
4422
+ if (entry === void 0 || entry === null) continue;
4423
+ normalized[key] = normalizeValue5(entry);
4424
+ }
4425
+ return normalized;
4426
+ }
4427
+ return value;
4428
+ }
4429
+ function normalizeSurfaceDefinition(definition) {
4430
+ const behavior = isPlainObject6(definition.behavior) ? normalizeValue5(definition.behavior) : { type: definition.type };
4431
+ return {
4432
+ type: definition.type,
4433
+ behavior,
4434
+ status: definition.status || "draft",
4435
+ environment: definition.environment || "development"
4436
+ };
4437
+ }
4438
+ async function computeSurfaceContentHash(definition) {
4439
+ const serialized = JSON.stringify(normalizeSurfaceDefinition(definition));
4440
+ const encoded = new TextEncoder().encode(serialized);
4441
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
4442
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
4443
+ }
4444
+ var DEFINE_SURFACE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
4445
+ "name",
4446
+ "type",
4447
+ "behavior",
4448
+ "inbound",
4449
+ "outbound",
4450
+ "status",
4451
+ "environment"
4452
+ ]);
4453
+ var SURFACE_DEFINITION_TYPES = /* @__PURE__ */ new Set([
4454
+ "chat",
4455
+ "mcp",
4456
+ "mcp_code",
4457
+ "api",
4458
+ "webhook",
4459
+ "schedule",
4460
+ "a2a",
4461
+ "email",
4462
+ "slack",
4463
+ "sms",
4464
+ "imessage",
4465
+ "discord",
4466
+ "whatsapp",
4467
+ "telegram",
4468
+ "hosted-page",
4469
+ "chrome_extension"
4470
+ ]);
4471
+ function defineSurface(input) {
4472
+ if (!input || typeof input !== "object") {
4473
+ throw new Error("defineSurface requires a definition object");
4474
+ }
4475
+ if (typeof input.name !== "string" || input.name.length === 0) {
4476
+ throw new Error('defineSurface requires a non-empty string "name"');
4477
+ }
4478
+ if (typeof input.type !== "string" || !SURFACE_DEFINITION_TYPES.has(input.type)) {
4479
+ throw new Error(
4480
+ `defineSurface requires "type" to be one of: ${[...SURFACE_DEFINITION_TYPES].join(", ")}`
4481
+ );
4482
+ }
4483
+ if (input.behavior !== void 0 && !isPlainObject6(input.behavior)) {
4484
+ throw new Error('defineSurface "behavior" must be an object when provided');
4485
+ }
4486
+ if (input.inbound !== void 0 && !isPlainObject6(input.inbound)) {
4487
+ throw new Error('defineSurface "inbound" must be an object when provided');
4488
+ }
4489
+ if (input.outbound !== void 0 && !isPlainObject6(input.outbound)) {
4490
+ throw new Error('defineSurface "outbound" must be an object when provided');
4491
+ }
4492
+ if (input.status !== void 0 && !["draft", "active", "paused"].includes(input.status)) {
4493
+ throw new Error('defineSurface "status" must be one of: draft, active, paused');
4494
+ }
4495
+ if (input.environment !== void 0 && !["production", "development"].includes(input.environment)) {
4496
+ throw new Error('defineSurface "environment" must be one of: production, development');
4497
+ }
4498
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SURFACE_TOP_LEVEL_KEYS.has(key));
4499
+ if (unknownKeys.length > 0) {
4500
+ throw new Error(
4501
+ `defineSurface: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, type, behavior, inbound, outbound, status, environment.`
4502
+ );
4503
+ }
4504
+ return {
4505
+ name: input.name,
4506
+ type: input.type,
4507
+ ...input.behavior !== void 0 ? { behavior: input.behavior } : {},
4508
+ ...input.inbound !== void 0 ? { inbound: input.inbound } : {},
4509
+ ...input.outbound !== void 0 ? { outbound: input.outbound } : {},
4510
+ ...input.status !== void 0 ? { status: input.status } : {},
4511
+ ...input.environment !== void 0 ? { environment: input.environment } : {}
4512
+ };
4513
+ }
4514
+ var SurfaceEnsureConflictError = class extends Error {
4515
+ constructor(body) {
4516
+ super(body.error ?? `Surface ensure conflict: ${body.code}`);
4517
+ this.name = "SurfaceEnsureConflictError";
4518
+ this.code = body.code;
4519
+ this.lastModifiedSource = body.lastModifiedSource;
4520
+ this.modifiedAt = body.modifiedAt;
4521
+ this.currentHash = body.currentHash;
4522
+ }
4523
+ };
4524
+ var SurfaceDriftError = class extends Error {
4525
+ constructor(plan) {
4526
+ super(
4527
+ `Surface "${plan.surfaceId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.surfaces.pull(productId, name) to absorb the remote edit into your repo, or re-run ensure to converge.`
4528
+ );
4529
+ this.name = "SurfaceDriftError";
4530
+ this.plan = plan;
4531
+ }
4532
+ };
4533
+ function parseRequestError6(err) {
4534
+ if (!(err instanceof Error)) return { status: null, body: null };
4535
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
4536
+ if (!match) return { status: null, body: null };
4537
+ try {
4538
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
4539
+ } catch {
4540
+ return { status: Number(match[1]), body: null };
4541
+ }
4542
+ }
4543
+ function toConflictError6(err) {
4544
+ const { status, body } = parseRequestError6(err);
4545
+ if (status !== 409 || !isPlainObject6(body)) return null;
4546
+ const code = body.code;
4547
+ if (code !== "external_modification" && code !== "remote_changed") return null;
4548
+ return new SurfaceEnsureConflictError(
4549
+ body
4550
+ );
4551
+ }
4552
+ var serverHashMemo6 = /* @__PURE__ */ new WeakMap();
4553
+ function memoFor6(client) {
4554
+ let memo = serverHashMemo6.get(client);
4555
+ if (!memo) {
4556
+ memo = /* @__PURE__ */ new Map();
4557
+ serverHashMemo6.set(client, memo);
4558
+ }
4559
+ return memo;
4560
+ }
4561
+ function memoize5(memo, memoKey, result) {
4562
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
4563
+ }
4564
+ async function request5(client, productId, body) {
4565
+ try {
4566
+ return await client.post(
4567
+ `/products/${encodeURIComponent(productId)}/surfaces/ensure`,
4568
+ body
4569
+ );
4570
+ } catch (err) {
4571
+ const conflict = toConflictError6(err);
4572
+ if (conflict) throw conflict;
4573
+ throw err;
4574
+ }
4575
+ }
4576
+ async function ensureSurface(client, productId, definition, options = {}) {
4577
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
4578
+ const passthrough = {
4579
+ ...onConflict ? { onConflict } : {},
4580
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
4581
+ };
4582
+ if (dryRun || expectNoChanges) {
4583
+ const plan = await request5(client, productId, {
4584
+ name: definition.name,
4585
+ definition,
4586
+ dryRun: true,
4587
+ ...passthrough
4588
+ });
4589
+ if (plan.result !== "plan") {
4590
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
4591
+ }
4592
+ if (expectNoChanges && plan.changes !== "none") {
4593
+ throw new SurfaceDriftError(plan);
4594
+ }
4595
+ return plan;
4596
+ }
4597
+ const memo = memoFor6(client);
4598
+ const localHash = await computeSurfaceContentHash(definition);
4599
+ const memoKey = `${productId} ${definition.name} ${localHash}`;
4600
+ const contentHash = memo.get(memoKey) ?? localHash;
4601
+ const probe = await request5(client, productId, {
4602
+ name: definition.name,
4603
+ contentHash,
4604
+ ...passthrough
4605
+ });
4606
+ if (probe.result !== "definitionRequired") {
4607
+ memoize5(memo, memoKey, probe);
4608
+ return probe;
4609
+ }
4610
+ const converged = await request5(client, productId, {
4611
+ name: definition.name,
4612
+ definition,
4613
+ ...passthrough
4614
+ });
4615
+ if (converged.result === "definitionRequired") {
4616
+ throw new Error("Server reported definitionRequired for a full-definition request");
4617
+ }
4618
+ memoize5(memo, memoKey, converged);
4619
+ return converged;
4620
+ }
4621
+ async function pullSurface(client, productId, name) {
4622
+ return client.get(
4623
+ `/products/${encodeURIComponent(productId)}/surfaces/pull`,
4624
+ { name }
4625
+ );
4626
+ }
4627
+
4628
+ // src/surfaces-namespace.ts
4629
+ var SurfacesNamespace = class {
4630
+ constructor(getClient) {
4631
+ this.getClient = getClient;
4632
+ }
4633
+ /**
4634
+ * Idempotently converge a `defineSurface` definition onto a product.
4635
+ * Hash-first: the steady state is one tiny probe request. Creates or updates
4636
+ * the surface; never deletes. Identity is name + product.
4637
+ *
4638
+ * @example
4639
+ * ```typescript
4640
+ * const chat = defineSurface({
4641
+ * name: 'Support Chat',
4642
+ * type: 'chat',
4643
+ * behavior: { type: 'chat', greeting: 'Hi there!' },
4644
+ * status: 'active',
4645
+ * })
4646
+ *
4647
+ * // Converge (CI/deploy).
4648
+ * const result = await Runtype.surfaces.ensure('product_abc', chat)
4649
+ *
4650
+ * // PR drift gate.
4651
+ * await Runtype.surfaces.ensure('product_abc', chat, { expectNoChanges: true })
4652
+ * ```
4653
+ */
4654
+ async ensure(productId, definition, options = {}) {
4655
+ return ensureSurface(this.getClient(), productId, definition, options);
4656
+ }
4657
+ /**
4658
+ * Pull the canonical definition + provenance for a surface by name within a
4659
+ * product — the absorb-drift direction of the ensure protocol.
4660
+ */
4661
+ async pull(productId, name) {
4662
+ return pullSurface(this.getClient(), productId, name);
4663
+ }
4664
+ };
4665
+
4162
4666
  // src/transform.ts
4163
4667
  function transformResponse(data) {
4164
4668
  return data;
@@ -4606,6 +5110,35 @@ var Runtype = class {
4606
5110
  static get products() {
4607
5111
  return new ProductsNamespace(() => this.getClient());
4608
5112
  }
5113
+ /**
5114
+ * Config-as-code operations for product surfaces. `surfaces.ensure` is the
5115
+ * deploy-time, non-executing converge (create-or-update a surface by name
5116
+ * within a product); `surfaces.pull` is the absorb-drift direction.
5117
+ *
5118
+ * @example
5119
+ * ```typescript
5120
+ * import { Runtype, defineSurface } from '@runtypelabs/sdk'
5121
+ *
5122
+ * const chat = defineSurface({
5123
+ * name: 'Support Chat',
5124
+ * type: 'chat',
5125
+ * behavior: { type: 'chat', greeting: 'Hi there!' },
5126
+ * status: 'active',
5127
+ * })
5128
+ *
5129
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
5130
+ * await Runtype.surfaces.ensure('product_abc', chat)
5131
+ *
5132
+ * // CI drift gate
5133
+ * await Runtype.surfaces.ensure('product_abc', chat, { expectNoChanges: true })
5134
+ *
5135
+ * // Absorb a dashboard edit back into the repo
5136
+ * const { definition } = await Runtype.surfaces.pull('product_abc', 'Support Chat')
5137
+ * ```
5138
+ */
5139
+ static get surfaces() {
5140
+ return new SurfacesNamespace(() => this.getClient());
5141
+ }
4609
5142
  };
4610
5143
 
4611
5144
  // src/generated-tool-gate.ts
@@ -4828,8 +5361,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
4828
5361
  ...decision.tool ? { tool: decision.tool } : {}
4829
5362
  };
4830
5363
  }
4831
- function attachRuntimeToolsToDispatchRequest(request4, runtimeTools, options = {}) {
4832
- const stepList = request4.flow.steps;
5364
+ function attachRuntimeToolsToDispatchRequest(request6, runtimeTools, options = {}) {
5365
+ const stepList = request6.flow.steps;
4833
5366
  if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
4834
5367
  throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
4835
5368
  }
@@ -4872,9 +5405,9 @@ function attachRuntimeToolsToDispatchRequest(request4, runtimeTools, options = {
4872
5405
  }
4873
5406
  };
4874
5407
  return {
4875
- ...request4,
5408
+ ...request6,
4876
5409
  flow: {
4877
- ...request4.flow,
5410
+ ...request6.flow,
4878
5411
  // `clonedSteps` is a structural clone of `request.flow.steps` (already
4879
5412
  // `FlowStepDefinition[]`); only the prompt step's `config.tools` was
4880
5413
  // merged, so every step's `type` discriminant is preserved. The clone is
@@ -4884,12 +5417,12 @@ function attachRuntimeToolsToDispatchRequest(request4, runtimeTools, options = {
4884
5417
  }
4885
5418
  };
4886
5419
  }
4887
- function applyGeneratedRuntimeToolProposalToDispatchRequest(request4, proposal, options = {}) {
5420
+ function applyGeneratedRuntimeToolProposalToDispatchRequest(request6, proposal, options = {}) {
4888
5421
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
4889
5422
  if (!decision.approved || !decision.tool) {
4890
- return { decision, request: request4 };
5423
+ return { decision, request: request6 };
4891
5424
  }
4892
- const nextRequest = attachRuntimeToolsToDispatchRequest(request4, [decision.tool], options.attach);
5425
+ const nextRequest = attachRuntimeToolsToDispatchRequest(request6, [decision.tool], options.attach);
4893
5426
  return {
4894
5427
  decision,
4895
5428
  request: nextRequest
@@ -7139,15 +7672,15 @@ var DispatchEndpoint = class {
7139
7672
  * Attach approved runtime tools to a prompt step in a redispatch request.
7140
7673
  * Returns a new request object and does not mutate the original.
7141
7674
  */
7142
- attachApprovedRuntimeTools(request4, runtimeTools, options) {
7143
- return attachRuntimeToolsToDispatchRequest(request4, runtimeTools, options);
7675
+ attachApprovedRuntimeTools(request6, runtimeTools, options) {
7676
+ return attachRuntimeToolsToDispatchRequest(request6, runtimeTools, options);
7144
7677
  }
7145
7678
  /**
7146
7679
  * Validate a generated runtime tool proposal and attach it to the redispatch
7147
7680
  * request if approved, in one call.
7148
7681
  */
7149
- applyGeneratedRuntimeToolProposal(request4, proposal, options) {
7150
- return applyGeneratedRuntimeToolProposalToDispatchRequest(request4, proposal, options);
7682
+ applyGeneratedRuntimeToolProposal(request6, proposal, options) {
7683
+ return applyGeneratedRuntimeToolProposalToDispatchRequest(request6, proposal, options);
7151
7684
  }
7152
7685
  };
7153
7686
  var ChatEndpoint = class {
@@ -7699,8 +8232,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
7699
8232
  },
7700
8233
  required: ["name", "description", "toolType", "parametersSchema", "config"]
7701
8234
  };
7702
- function appendRuntimeToolsToAgentRequest(request4, runtimeTools) {
7703
- const existing = request4.tools?.runtimeTools || [];
8235
+ function appendRuntimeToolsToAgentRequest(request6, runtimeTools) {
8236
+ const existing = request6.tools?.runtimeTools || [];
7704
8237
  const existingNames = new Set(existing.map((tool) => tool.name));
7705
8238
  const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
7706
8239
  name: tool.name,
@@ -7710,9 +8243,9 @@ function appendRuntimeToolsToAgentRequest(request4, runtimeTools) {
7710
8243
  ...tool.config ? { config: tool.config } : {}
7711
8244
  }));
7712
8245
  return {
7713
- ...request4,
8246
+ ...request6,
7714
8247
  tools: {
7715
- ...request4.tools,
8248
+ ...request6.tools,
7716
8249
  runtimeTools: [...existing, ...converted]
7717
8250
  }
7718
8251
  };
@@ -7788,21 +8321,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7788
8321
  * Attach approved runtime tools to an agent execute request.
7789
8322
  * Returns a new request object and does not mutate the original.
7790
8323
  */
7791
- attachApprovedRuntimeTools(request4, runtimeTools) {
7792
- return appendRuntimeToolsToAgentRequest(request4, runtimeTools);
8324
+ attachApprovedRuntimeTools(request6, runtimeTools) {
8325
+ return appendRuntimeToolsToAgentRequest(request6, runtimeTools);
7793
8326
  }
7794
8327
  /**
7795
8328
  * Validate a generated runtime tool proposal and append it to an agent execute
7796
8329
  * request if approved, in one call.
7797
8330
  */
7798
- applyGeneratedRuntimeToolProposal(request4, proposal, options) {
8331
+ applyGeneratedRuntimeToolProposal(request6, proposal, options) {
7799
8332
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
7800
8333
  if (!decision.approved || !decision.tool) {
7801
- return { decision, request: request4 };
8334
+ return { decision, request: request6 };
7802
8335
  }
7803
8336
  return {
7804
8337
  decision,
7805
- request: appendRuntimeToolsToAgentRequest(request4, [decision.tool])
8338
+ request: appendRuntimeToolsToAgentRequest(request6, [decision.tool])
7806
8339
  };
7807
8340
  }
7808
8341
  /**
@@ -10903,6 +11436,29 @@ var BillingEndpoint = class {
10903
11436
  return this.client.get("/billing/spend-analytics", params);
10904
11437
  }
10905
11438
  };
11439
+ var ToolApprovalGrantsEndpoint = class {
11440
+ constructor(client) {
11441
+ this.client = client;
11442
+ }
11443
+ /**
11444
+ * List active remembered tool-approval grants for the authenticated owner,
11445
+ * optionally filtered to a single agent.
11446
+ */
11447
+ async list(agentId) {
11448
+ const query = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
11449
+ const response = await this.client.get(
11450
+ `/tool-approval-grants${query}`
11451
+ );
11452
+ return response.data;
11453
+ }
11454
+ /**
11455
+ * Revoke (soft-delete) a remembered grant so the tool prompts for approval
11456
+ * again on future dispatches.
11457
+ */
11458
+ async revoke(id) {
11459
+ return this.client.delete(`/tool-approval-grants/${id}`);
11460
+ }
11461
+ };
10906
11462
  var AppsEndpoint = class {
10907
11463
  constructor(client) {
10908
11464
  this.client = client;
@@ -11000,6 +11556,7 @@ var RuntypeClient2 = class {
11000
11556
  this.flowVersions = new FlowVersionsEndpoint(this);
11001
11557
  this.integrations = new IntegrationsEndpoint(this);
11002
11558
  this.billing = new BillingEndpoint(this);
11559
+ this.toolApprovalGrants = new ToolApprovalGrantsEndpoint(this);
11003
11560
  }
11004
11561
  /**
11005
11562
  * Set the API key for authentication
@@ -11038,7 +11595,7 @@ var RuntypeClient2 = class {
11038
11595
  clearApiKey() {
11039
11596
  delete this.headers.Authorization;
11040
11597
  }
11041
- async runWithLocalTools(request4, localTools, arg3, arg4) {
11598
+ async runWithLocalTools(request6, localTools, arg3, arg4) {
11042
11599
  const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
11043
11600
  const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
11044
11601
  const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
@@ -11052,12 +11609,12 @@ var RuntypeClient2 = class {
11052
11609
  ...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
11053
11610
  })) : [];
11054
11611
  const modifiedRequest = {
11055
- ...request4,
11612
+ ...request6,
11056
11613
  ...derivedClientTools.length > 0 ? {
11057
- clientTools: [...request4.clientTools ?? [], ...derivedClientTools]
11614
+ clientTools: [...request6.clientTools ?? [], ...derivedClientTools]
11058
11615
  } : {},
11059
11616
  options: {
11060
- ...request4.options || {},
11617
+ ...request6.options || {},
11061
11618
  streamResponse: isStreaming
11062
11619
  }
11063
11620
  };
@@ -11217,6 +11774,30 @@ var RuntypeClient2 = class {
11217
11774
  });
11218
11775
  return transformResponse(response);
11219
11776
  }
11777
+ /**
11778
+ * Conditional GET (`ETag` / `If-None-Match`).
11779
+ *
11780
+ * Unlike {@link get}, this does NOT throw on `304` — it surfaces the not-modified
11781
+ * outcome so callers can keep their cached copy. Used by the dashboard's primary
11782
+ * lists to revalidate the canonical first page in a single round trip on both
11783
+ * the changed and unchanged paths. The `304` path returns no body; the `200`
11784
+ * path returns the parsed body plus the server's fresh `ETag`.
11785
+ */
11786
+ async getConditional(path, params, ifNoneMatch) {
11787
+ const url = this.buildUrl(path, params);
11788
+ const headers = { ...this.headers };
11789
+ if (ifNoneMatch) headers["If-None-Match"] = ifNoneMatch;
11790
+ const response = await this.fetchWithTimeout(url, { method: "GET", headers });
11791
+ const etag = response.headers.get("ETag");
11792
+ if (response.status === 304) {
11793
+ return { notModified: true, etag };
11794
+ }
11795
+ if (!response.ok) {
11796
+ throw await this.createApiError(response);
11797
+ }
11798
+ const data = transformResponse(await response.json());
11799
+ return { notModified: false, etag, data };
11800
+ }
11220
11801
  /**
11221
11802
  * Generic POST request
11222
11803
  */
@@ -11335,7 +11916,15 @@ var RuntypeClient2 = class {
11335
11916
  /**
11336
11917
  * Make HTTP request with timeout and error handling
11337
11918
  */
11338
- async makeRequest(url, options) {
11919
+ /**
11920
+ * Run a fetch under the client timeout and return the raw `Response`. Maps a
11921
+ * timeout-driven `AbortError` to a descriptive timeout `Error`; does NOT inspect
11922
+ * status, so callers decide how to treat non-2xx (throw, intercept `304`, etc.).
11923
+ *
11924
+ * `makeRawRequest` keeps its own variant: it additionally composes a
11925
+ * caller-supplied `signal` (user-initiated stream aborts) with the timeout.
11926
+ */
11927
+ async fetchWithTimeout(url, options) {
11339
11928
  const controller = this.timeout === null ? null : new AbortController();
11340
11929
  const timeoutId = controller && this.timeout !== null ? setTimeout(() => controller.abort(), this.timeout) : null;
11341
11930
  try {
@@ -11344,17 +11933,7 @@ var RuntypeClient2 = class {
11344
11933
  ...controller ? { signal: controller.signal } : {}
11345
11934
  });
11346
11935
  if (timeoutId) clearTimeout(timeoutId);
11347
- if (!response.ok) {
11348
- throw await this.createApiError(response);
11349
- }
11350
- if (response.status === 204) {
11351
- return null;
11352
- }
11353
- const contentType = response.headers.get("content-type");
11354
- if (contentType?.includes("application/json")) {
11355
- return response.json();
11356
- }
11357
- return response.text();
11936
+ return response;
11358
11937
  } catch (error) {
11359
11938
  if (timeoutId) clearTimeout(timeoutId);
11360
11939
  if (timeoutId && error instanceof Error && error.name === "AbortError") {
@@ -11363,6 +11942,20 @@ var RuntypeClient2 = class {
11363
11942
  throw error;
11364
11943
  }
11365
11944
  }
11945
+ async makeRequest(url, options) {
11946
+ const response = await this.fetchWithTimeout(url, options);
11947
+ if (!response.ok) {
11948
+ throw await this.createApiError(response);
11949
+ }
11950
+ if (response.status === 204) {
11951
+ return null;
11952
+ }
11953
+ const contentType = response.headers.get("content-type");
11954
+ if (contentType?.includes("application/json")) {
11955
+ return response.json();
11956
+ }
11957
+ return response.text();
11958
+ }
11366
11959
  /**
11367
11960
  * Make HTTP request that returns raw Response (for streaming)
11368
11961
  */
@@ -11495,20 +12088,20 @@ var BatchBuilder = class {
11495
12088
  if (!this.recordType) {
11496
12089
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
11497
12090
  }
11498
- const request4 = {
12091
+ const request6 = {
11499
12092
  flowId: this.flowId,
11500
12093
  recordType: this.recordType
11501
12094
  };
11502
12095
  if (Object.keys(this.batchOptions).length > 0) {
11503
- request4.options = this.batchOptions;
12096
+ request6.options = this.batchOptions;
11504
12097
  }
11505
12098
  if (this.filterConfig) {
11506
- request4.filter = this.filterConfig;
12099
+ request6.filter = this.filterConfig;
11507
12100
  }
11508
12101
  if (this.limitConfig !== void 0) {
11509
- request4.limit = this.limitConfig;
12102
+ request6.limit = this.limitConfig;
11510
12103
  }
11511
- return request4;
12104
+ return request6;
11512
12105
  }
11513
12106
  /**
11514
12107
  * Execute the batch operation
@@ -11665,32 +12258,32 @@ var EvalBuilder = class {
11665
12258
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
11666
12259
  );
11667
12260
  }
11668
- const request4 = {};
12261
+ const request6 = {};
11669
12262
  if (this.flowId) {
11670
- request4.flowId = this.flowId;
12263
+ request6.flowId = this.flowId;
11671
12264
  } else if (this.virtualFlow) {
11672
- request4.flow = this.virtualFlow;
12265
+ request6.flow = this.virtualFlow;
11673
12266
  }
11674
12267
  if (this.recordType) {
11675
- request4.recordType = this.recordType;
12268
+ request6.recordType = this.recordType;
11676
12269
  } else if (this.inlineRecords) {
11677
- request4.records = this.inlineRecords;
12270
+ request6.records = this.inlineRecords;
11678
12271
  }
11679
12272
  if (this.modelOverrides) {
11680
- request4.modelOverrides = this.modelOverrides;
12273
+ request6.modelOverrides = this.modelOverrides;
11681
12274
  } else if (this.modelConfigs) {
11682
- request4.modelConfigs = this.modelConfigs;
12275
+ request6.modelConfigs = this.modelConfigs;
11683
12276
  }
11684
12277
  if (Object.keys(this.evalOptions).length > 0) {
11685
- request4.options = this.evalOptions;
12278
+ request6.options = this.evalOptions;
11686
12279
  }
11687
12280
  if (this.filterConfig) {
11688
- request4.filter = this.filterConfig;
12281
+ request6.filter = this.filterConfig;
11689
12282
  }
11690
12283
  if (this.limitConfig !== void 0) {
11691
- request4.limit = this.limitConfig;
12284
+ request6.limit = this.limitConfig;
11692
12285
  }
11693
- return request4;
12286
+ return request6;
11694
12287
  }
11695
12288
  /**
11696
12289
  * Execute the evaluation
@@ -12210,9 +12803,15 @@ var STEP_TYPE_TO_METHOD = {
12210
12803
  STEP_TYPE_TO_METHOD,
12211
12804
  SchedulesEndpoint,
12212
12805
  SecretsEndpoint,
12806
+ SkillDriftError,
12807
+ SkillEnsureConflictError,
12213
12808
  SkillProposalsNamespace,
12214
12809
  SkillsNamespace,
12810
+ SurfaceDriftError,
12811
+ SurfaceEnsureConflictError,
12215
12812
  SurfacesEndpoint,
12813
+ SurfacesNamespace,
12814
+ ToolApprovalGrantsEndpoint,
12216
12815
  ToolDriftError,
12217
12816
  ToolEnsureConflictError,
12218
12817
  ToolsEndpoint,
@@ -12229,6 +12828,8 @@ var STEP_TYPE_TO_METHOD = {
12229
12828
  computeAgentContentHash,
12230
12829
  computeFlowContentHash,
12231
12830
  computeProductContentHash,
12831
+ computeSkillContentHash,
12832
+ computeSurfaceContentHash,
12232
12833
  computeToolContentHash,
12233
12834
  createClient,
12234
12835
  createExternalTool,
@@ -12238,6 +12839,8 @@ var STEP_TYPE_TO_METHOD = {
12238
12839
  defineFlow,
12239
12840
  definePlaybook,
12240
12841
  defineProduct,
12842
+ defineSkill,
12843
+ defineSurface,
12241
12844
  defineTool,
12242
12845
  deployWorkflow,
12243
12846
  ensureDefaultWorkflowHooks,
@@ -12255,6 +12858,8 @@ var STEP_TYPE_TO_METHOD = {
12255
12858
  normalizeAgentDefinition,
12256
12859
  normalizeCandidatePath,
12257
12860
  normalizeProductDefinition,
12861
+ normalizeSkillDefinition,
12862
+ normalizeSurfaceDefinition,
12258
12863
  normalizeToolDefinition,
12259
12864
  parseFinalBuffer,
12260
12865
  parseLedgerArtifactRelativePath,