@runtypelabs/sdk 4.15.0 → 4.16.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
  };
@@ -11495,20 +12052,20 @@ var BatchBuilder = class {
11495
12052
  if (!this.recordType) {
11496
12053
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
11497
12054
  }
11498
- const request4 = {
12055
+ const request6 = {
11499
12056
  flowId: this.flowId,
11500
12057
  recordType: this.recordType
11501
12058
  };
11502
12059
  if (Object.keys(this.batchOptions).length > 0) {
11503
- request4.options = this.batchOptions;
12060
+ request6.options = this.batchOptions;
11504
12061
  }
11505
12062
  if (this.filterConfig) {
11506
- request4.filter = this.filterConfig;
12063
+ request6.filter = this.filterConfig;
11507
12064
  }
11508
12065
  if (this.limitConfig !== void 0) {
11509
- request4.limit = this.limitConfig;
12066
+ request6.limit = this.limitConfig;
11510
12067
  }
11511
- return request4;
12068
+ return request6;
11512
12069
  }
11513
12070
  /**
11514
12071
  * Execute the batch operation
@@ -11665,32 +12222,32 @@ var EvalBuilder = class {
11665
12222
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
11666
12223
  );
11667
12224
  }
11668
- const request4 = {};
12225
+ const request6 = {};
11669
12226
  if (this.flowId) {
11670
- request4.flowId = this.flowId;
12227
+ request6.flowId = this.flowId;
11671
12228
  } else if (this.virtualFlow) {
11672
- request4.flow = this.virtualFlow;
12229
+ request6.flow = this.virtualFlow;
11673
12230
  }
11674
12231
  if (this.recordType) {
11675
- request4.recordType = this.recordType;
12232
+ request6.recordType = this.recordType;
11676
12233
  } else if (this.inlineRecords) {
11677
- request4.records = this.inlineRecords;
12234
+ request6.records = this.inlineRecords;
11678
12235
  }
11679
12236
  if (this.modelOverrides) {
11680
- request4.modelOverrides = this.modelOverrides;
12237
+ request6.modelOverrides = this.modelOverrides;
11681
12238
  } else if (this.modelConfigs) {
11682
- request4.modelConfigs = this.modelConfigs;
12239
+ request6.modelConfigs = this.modelConfigs;
11683
12240
  }
11684
12241
  if (Object.keys(this.evalOptions).length > 0) {
11685
- request4.options = this.evalOptions;
12242
+ request6.options = this.evalOptions;
11686
12243
  }
11687
12244
  if (this.filterConfig) {
11688
- request4.filter = this.filterConfig;
12245
+ request6.filter = this.filterConfig;
11689
12246
  }
11690
12247
  if (this.limitConfig !== void 0) {
11691
- request4.limit = this.limitConfig;
12248
+ request6.limit = this.limitConfig;
11692
12249
  }
11693
- return request4;
12250
+ return request6;
11694
12251
  }
11695
12252
  /**
11696
12253
  * Execute the evaluation
@@ -12210,9 +12767,15 @@ var STEP_TYPE_TO_METHOD = {
12210
12767
  STEP_TYPE_TO_METHOD,
12211
12768
  SchedulesEndpoint,
12212
12769
  SecretsEndpoint,
12770
+ SkillDriftError,
12771
+ SkillEnsureConflictError,
12213
12772
  SkillProposalsNamespace,
12214
12773
  SkillsNamespace,
12774
+ SurfaceDriftError,
12775
+ SurfaceEnsureConflictError,
12215
12776
  SurfacesEndpoint,
12777
+ SurfacesNamespace,
12778
+ ToolApprovalGrantsEndpoint,
12216
12779
  ToolDriftError,
12217
12780
  ToolEnsureConflictError,
12218
12781
  ToolsEndpoint,
@@ -12229,6 +12792,8 @@ var STEP_TYPE_TO_METHOD = {
12229
12792
  computeAgentContentHash,
12230
12793
  computeFlowContentHash,
12231
12794
  computeProductContentHash,
12795
+ computeSkillContentHash,
12796
+ computeSurfaceContentHash,
12232
12797
  computeToolContentHash,
12233
12798
  createClient,
12234
12799
  createExternalTool,
@@ -12238,6 +12803,8 @@ var STEP_TYPE_TO_METHOD = {
12238
12803
  defineFlow,
12239
12804
  definePlaybook,
12240
12805
  defineProduct,
12806
+ defineSkill,
12807
+ defineSurface,
12241
12808
  defineTool,
12242
12809
  deployWorkflow,
12243
12810
  ensureDefaultWorkflowHooks,
@@ -12255,6 +12822,8 @@ var STEP_TYPE_TO_METHOD = {
12255
12822
  normalizeAgentDefinition,
12256
12823
  normalizeCandidatePath,
12257
12824
  normalizeProductDefinition,
12825
+ normalizeSkillDefinition,
12826
+ normalizeSurfaceDefinition,
12258
12827
  normalizeToolDefinition,
12259
12828
  parseFinalBuffer,
12260
12829
  parseLedgerArtifactRelativePath,