@runtypelabs/sdk 4.14.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.mjs CHANGED
@@ -1094,20 +1094,20 @@ var FlowBuilder = class {
1094
1094
  */
1095
1095
  build() {
1096
1096
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
1097
- const request3 = { flow };
1097
+ const request6 = { flow };
1098
1098
  if (this.recordConfig) {
1099
- request3.record = this.recordConfig;
1099
+ request6.record = this.recordConfig;
1100
1100
  }
1101
1101
  if (this.messagesConfig) {
1102
- request3.messages = this.messagesConfig;
1102
+ request6.messages = this.messagesConfig;
1103
1103
  }
1104
1104
  if (this.inputsConfig) {
1105
- request3.inputs = this.inputsConfig;
1105
+ request6.inputs = this.inputsConfig;
1106
1106
  }
1107
1107
  if (Object.keys(this.optionsConfig).length > 0) {
1108
- request3.options = this.optionsConfig;
1108
+ request6.options = this.optionsConfig;
1109
1109
  }
1110
- return request3;
1110
+ return request6;
1111
1111
  }
1112
1112
  /**
1113
1113
  * Validate this prospective flow against the public validation endpoint
@@ -2501,15 +2501,15 @@ var RuntypeFlowBuilder = class {
2501
2501
  build() {
2502
2502
  const flowMode = this.mode === "existing" ? "existing" : this.mode;
2503
2503
  const flow = this.existingFlowId ? { id: this.existingFlowId } : { name: this.flowConfig.name, steps: this.steps };
2504
- const request3 = { flow };
2504
+ const request6 = { flow };
2505
2505
  if (this.recordConfig) {
2506
- request3.record = this.recordConfig;
2506
+ request6.record = this.recordConfig;
2507
2507
  }
2508
2508
  if (this.messagesConfig) {
2509
- request3.messages = this.messagesConfig;
2509
+ request6.messages = this.messagesConfig;
2510
2510
  }
2511
2511
  if (this.inputsConfig) {
2512
- request3.inputs = this.inputsConfig;
2512
+ request6.inputs = this.inputsConfig;
2513
2513
  }
2514
2514
  const options = {
2515
2515
  flowMode,
@@ -2527,8 +2527,8 @@ var RuntypeFlowBuilder = class {
2527
2527
  if (this.mode === "upsert" && Object.keys(this.upsertOptions).length > 0) {
2528
2528
  options.upsertOptions = this.upsertOptions;
2529
2529
  }
2530
- request3.options = options;
2531
- return request3;
2530
+ request6.options = options;
2531
+ return request6;
2532
2532
  }
2533
2533
  /**
2534
2534
  * Validate this prospective flow against the public validation endpoint
@@ -3122,6 +3122,192 @@ var PromptsNamespace = class {
3122
3122
  }
3123
3123
  };
3124
3124
 
3125
+ // src/skills-ensure.ts
3126
+ function isPlainObject2(value) {
3127
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3128
+ }
3129
+ function normalizeValue(value) {
3130
+ if (Array.isArray(value)) {
3131
+ return value.map((item) => normalizeValue(item));
3132
+ }
3133
+ if (isPlainObject2(value)) {
3134
+ const normalized = {};
3135
+ for (const key of Object.keys(value).sort()) {
3136
+ const entry = value[key];
3137
+ if (entry === void 0 || entry === null) continue;
3138
+ normalized[key] = normalizeValue(entry);
3139
+ }
3140
+ return normalized;
3141
+ }
3142
+ return value;
3143
+ }
3144
+ function normalizeSkillDefinition(definition) {
3145
+ const manifest = isPlainObject2(definition.manifest) ? definition.manifest : {};
3146
+ const rawFrontmatter = isPlainObject2(manifest.frontmatter) ? manifest.frontmatter : {};
3147
+ const frontmatterWithoutName = {};
3148
+ for (const key of Object.keys(rawFrontmatter)) {
3149
+ if (key === "name") continue;
3150
+ frontmatterWithoutName[key] = rawFrontmatter[key];
3151
+ }
3152
+ const frontmatter = normalizeValue(frontmatterWithoutName);
3153
+ const runtype = isPlainObject2(manifest.runtype) ? normalizeValue(manifest.runtype) : {};
3154
+ const body = typeof manifest.body === "string" ? manifest.body : "";
3155
+ return { frontmatter, runtype, body };
3156
+ }
3157
+ async function computeSkillContentHash(definition) {
3158
+ const serialized = JSON.stringify(normalizeSkillDefinition(definition));
3159
+ const encoded = new TextEncoder().encode(serialized);
3160
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3161
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3162
+ }
3163
+ var DEFINE_SKILL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "manifest"]);
3164
+ function defineSkill(input) {
3165
+ if (!input || typeof input !== "object") {
3166
+ throw new Error("defineSkill requires a definition object");
3167
+ }
3168
+ if (typeof input.name !== "string" || input.name.length === 0) {
3169
+ throw new Error('defineSkill requires a non-empty string "name"');
3170
+ }
3171
+ if (!isPlainObject2(input.manifest)) {
3172
+ throw new Error('defineSkill requires a "manifest" object ({ frontmatter, runtype, body })');
3173
+ }
3174
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SKILL_TOP_LEVEL_KEYS.has(key));
3175
+ if (unknownKeys.length > 0) {
3176
+ throw new Error(
3177
+ `defineSkill: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name and manifest.`
3178
+ );
3179
+ }
3180
+ const frontmatter = input.manifest.frontmatter;
3181
+ if (!isPlainObject2(frontmatter) || typeof frontmatter.name !== "string") {
3182
+ throw new Error("defineSkill: manifest.frontmatter.name is required");
3183
+ }
3184
+ if (frontmatter.name !== input.name) {
3185
+ throw new Error(
3186
+ `defineSkill: manifest.frontmatter.name ("${frontmatter.name}") must match the identity name ("${input.name}").`
3187
+ );
3188
+ }
3189
+ return { name: input.name, manifest: input.manifest };
3190
+ }
3191
+ var SkillEnsureConflictError = class extends Error {
3192
+ constructor(body) {
3193
+ super(body.error ?? `Skill ensure conflict: ${body.code}`);
3194
+ this.name = "SkillEnsureConflictError";
3195
+ this.code = body.code;
3196
+ this.lastModifiedSource = body.lastModifiedSource;
3197
+ this.modifiedAt = body.modifiedAt;
3198
+ this.currentHash = body.currentHash;
3199
+ }
3200
+ };
3201
+ var SkillDriftError = class extends Error {
3202
+ constructor(plan) {
3203
+ super(
3204
+ `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.`
3205
+ );
3206
+ this.name = "SkillDriftError";
3207
+ this.plan = plan;
3208
+ }
3209
+ };
3210
+ function parseRequestError2(err) {
3211
+ if (!(err instanceof Error)) return { status: null, body: null };
3212
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3213
+ if (!match) return { status: null, body: null };
3214
+ try {
3215
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3216
+ } catch {
3217
+ return { status: Number(match[1]), body: null };
3218
+ }
3219
+ }
3220
+ function toConflictError2(err) {
3221
+ const { status, body } = parseRequestError2(err);
3222
+ if (status !== 409 || !isPlainObject2(body)) return null;
3223
+ const code = body.code;
3224
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3225
+ return new SkillEnsureConflictError(
3226
+ body
3227
+ );
3228
+ }
3229
+ var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3230
+ function memoFor2(client) {
3231
+ let memo = serverHashMemo2.get(client);
3232
+ if (!memo) {
3233
+ memo = /* @__PURE__ */ new Map();
3234
+ serverHashMemo2.set(client, memo);
3235
+ }
3236
+ return memo;
3237
+ }
3238
+ function memoize2(memo, memoKey, result) {
3239
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3240
+ }
3241
+ async function request2(client, body) {
3242
+ try {
3243
+ return await client.post(
3244
+ "/skills/ensure",
3245
+ body
3246
+ );
3247
+ } catch (err) {
3248
+ const conflict = toConflictError2(err);
3249
+ if (conflict) throw conflict;
3250
+ throw err;
3251
+ }
3252
+ }
3253
+ async function ensureSkill(client, definition, options = {}) {
3254
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3255
+ const passthrough = {
3256
+ ...onConflict ? { onConflict } : {},
3257
+ ...release ? { release } : {},
3258
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3259
+ };
3260
+ if (dryRun || expectNoChanges) {
3261
+ const plan = await request2(client, {
3262
+ name: definition.name,
3263
+ definition: manifestToWire(definition),
3264
+ dryRun: true,
3265
+ ...passthrough
3266
+ });
3267
+ if (plan.result !== "plan") {
3268
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3269
+ }
3270
+ if (expectNoChanges && plan.changes !== "none") {
3271
+ throw new SkillDriftError(plan);
3272
+ }
3273
+ return plan;
3274
+ }
3275
+ const memo = memoFor2(client);
3276
+ const localHash = await computeSkillContentHash(definition);
3277
+ const memoKey = `${definition.name} ${localHash}`;
3278
+ const contentHash = memo.get(memoKey) ?? localHash;
3279
+ const probe = await request2(client, {
3280
+ name: definition.name,
3281
+ contentHash,
3282
+ ...passthrough
3283
+ });
3284
+ if (probe.result !== "definitionRequired") {
3285
+ memoize2(memo, memoKey, probe);
3286
+ return probe;
3287
+ }
3288
+ const converged = await request2(client, {
3289
+ name: definition.name,
3290
+ definition: manifestToWire(definition),
3291
+ ...passthrough
3292
+ });
3293
+ if (converged.result === "definitionRequired") {
3294
+ throw new Error("Server reported definitionRequired for a full-definition request");
3295
+ }
3296
+ memoize2(memo, memoKey, converged);
3297
+ return converged;
3298
+ }
3299
+ function manifestToWire(definition) {
3300
+ const manifest = definition.manifest;
3301
+ const frontmatter = { ...manifest.frontmatter };
3302
+ if (manifest.runtype && Object.keys(manifest.runtype).length > 0) {
3303
+ frontmatter.runtype = manifest.runtype;
3304
+ }
3305
+ return { frontmatter, body: manifest.body ?? "" };
3306
+ }
3307
+ async function pullSkill(client, name) {
3308
+ return client.get("/skills/pull", { name });
3309
+ }
3310
+
3125
3311
  // src/skills-namespace.ts
3126
3312
  var SkillProposalsNamespace = class {
3127
3313
  constructor(getClient) {
@@ -3262,6 +3448,22 @@ var SkillsNamespace = class {
3262
3448
  const client = this.getClient();
3263
3449
  await client.post(`/skills/${skillId}/versions/${versionId}/publish`);
3264
3450
  }
3451
+ /**
3452
+ * Statically scan a SKILL.md document for malicious patterns and return a
3453
+ * two-tier verdict — `warning` when a skill appears suspicious (low–medium
3454
+ * confidence), `error` when high-confidence malicious. Does not persist or
3455
+ * gate; use it as a "scan before save" affordance.
3456
+ *
3457
+ * @example
3458
+ * ```typescript
3459
+ * const { verdict } = await Runtype.skills.scan(skillMarkdown)
3460
+ * if (verdict.tier === 'error') console.warn(verdict.summary)
3461
+ * ```
3462
+ */
3463
+ async scan(markdown) {
3464
+ const client = this.getClient();
3465
+ return client.post("/skills/scan", { markdown });
3466
+ }
3265
3467
  /**
3266
3468
  * Import a single SKILL.md document. The imported skill lands with
3267
3469
  * `trustLevel: 'imported'` and a draft version.
@@ -3304,6 +3506,40 @@ var SkillsNamespace = class {
3304
3506
  const res = await client.get("/skills/bindings", { agentId });
3305
3507
  return res.data;
3306
3508
  }
3509
+ /**
3510
+ * Idempotently converge a `defineSkill` definition onto the platform.
3511
+ * Hash-first: the steady state is one tiny probe request. Creates or appends a
3512
+ * new version; never deletes. Identity is name + account scope. Pass
3513
+ * `release: 'publish'` to publish the converged version.
3514
+ *
3515
+ * @example
3516
+ * ```typescript
3517
+ * const reviewer = defineSkill({
3518
+ * name: 'code_reviewer',
3519
+ * manifest: {
3520
+ * frontmatter: { name: 'code_reviewer', description: 'Reviews pull requests' },
3521
+ * runtype: { trustLevel: 'org' },
3522
+ * body: '# Code Reviewer\n\nReview the diff...',
3523
+ * },
3524
+ * })
3525
+ *
3526
+ * // Converge + publish (CI/deploy).
3527
+ * const result = await Runtype.skills.ensure(reviewer, { release: 'publish' })
3528
+ *
3529
+ * // PR drift gate.
3530
+ * await Runtype.skills.ensure(reviewer, { expectNoChanges: true })
3531
+ * ```
3532
+ */
3533
+ async ensure(definition, options = {}) {
3534
+ return ensureSkill(this.getClient(), definition, options);
3535
+ }
3536
+ /**
3537
+ * Pull the canonical definition + provenance for a skill by name — the
3538
+ * absorb-drift direction of the ensure protocol.
3539
+ */
3540
+ async pull(name) {
3541
+ return pullSkill(this.getClient(), name);
3542
+ }
3307
3543
  };
3308
3544
 
3309
3545
  // src/agents-namespace.ts
@@ -3329,19 +3565,19 @@ var AGENT_CONFIG_KEYS = [
3329
3565
  "memory"
3330
3566
  ];
3331
3567
  var AGENT_CONFIG_KEY_LIST = [...AGENT_CONFIG_KEYS].sort();
3332
- function isPlainObject2(value) {
3568
+ function isPlainObject3(value) {
3333
3569
  return value !== null && typeof value === "object" && !Array.isArray(value);
3334
3570
  }
3335
- function normalizeValue(value) {
3571
+ function normalizeValue2(value) {
3336
3572
  if (Array.isArray(value)) {
3337
- return value.map((item) => normalizeValue(item));
3573
+ return value.map((item) => normalizeValue2(item));
3338
3574
  }
3339
- if (isPlainObject2(value)) {
3575
+ if (isPlainObject3(value)) {
3340
3576
  const normalized = {};
3341
3577
  for (const key of Object.keys(value).sort()) {
3342
3578
  const entry = value[key];
3343
3579
  if (entry === void 0 || entry === null) continue;
3344
- normalized[key] = normalizeValue(entry);
3580
+ normalized[key] = normalizeValue2(entry);
3345
3581
  }
3346
3582
  return normalized;
3347
3583
  }
@@ -3349,11 +3585,11 @@ function normalizeValue(value) {
3349
3585
  }
3350
3586
  function normalizeAgentDefinition(definition) {
3351
3587
  const config = {};
3352
- const rawConfig = isPlainObject2(definition.config) ? definition.config : {};
3588
+ const rawConfig = isPlainObject3(definition.config) ? definition.config : {};
3353
3589
  for (const key of AGENT_CONFIG_KEY_LIST) {
3354
3590
  const value = rawConfig[key];
3355
3591
  if (value === void 0 || value === null) continue;
3356
- config[key] = normalizeValue(value);
3592
+ config[key] = normalizeValue2(value);
3357
3593
  }
3358
3594
  return {
3359
3595
  name: definition.name,
@@ -3371,7 +3607,7 @@ async function computeAgentContentHash(definition) {
3371
3607
  var DEFINE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "description", "icon", ...AGENT_CONFIG_KEYS]);
3372
3608
  function collectNonPortableToolRefs(config) {
3373
3609
  const tools = config.tools;
3374
- if (!isPlainObject2(tools)) return [];
3610
+ if (!isPlainObject3(tools)) return [];
3375
3611
  const found = [];
3376
3612
  const isAccountScoped = (ref) => typeof ref === "string" && ref.startsWith("tool_");
3377
3613
  const scanArray = (value, path) => {
@@ -3381,7 +3617,7 @@ function collectNonPortableToolRefs(config) {
3381
3617
  });
3382
3618
  };
3383
3619
  const scanKeys = (value, path) => {
3384
- if (!isPlainObject2(value)) return;
3620
+ if (!isPlainObject3(value)) return;
3385
3621
  for (const key of Object.keys(value)) {
3386
3622
  if (isAccountScoped(key)) found.push(`${path}.${key}`);
3387
3623
  }
@@ -3389,16 +3625,16 @@ function collectNonPortableToolRefs(config) {
3389
3625
  scanArray(tools.toolIds, "tools.toolIds");
3390
3626
  scanKeys(tools.toolConfigs, "tools.toolConfigs");
3391
3627
  scanKeys(tools.perToolLimits, "tools.perToolLimits");
3392
- if (isPlainObject2(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3393
- if (isPlainObject2(tools.subagentConfig)) {
3628
+ if (isPlainObject3(tools.approval)) scanArray(tools.approval.require, "tools.approval.require");
3629
+ if (isPlainObject3(tools.subagentConfig)) {
3394
3630
  scanArray(tools.subagentConfig.toolPool, "tools.subagentConfig.toolPool");
3395
3631
  }
3396
- if (isPlainObject2(tools.codeModeConfig)) {
3632
+ if (isPlainObject3(tools.codeModeConfig)) {
3397
3633
  scanArray(tools.codeModeConfig.toolPool, "tools.codeModeConfig.toolPool");
3398
3634
  }
3399
3635
  if (Array.isArray(tools.runtimeTools)) {
3400
3636
  tools.runtimeTools.forEach((runtimeTool, i) => {
3401
- if (!isPlainObject2(runtimeTool) || !isPlainObject2(runtimeTool.config)) return;
3637
+ if (!isPlainObject3(runtimeTool) || !isPlainObject3(runtimeTool.config)) return;
3402
3638
  const base = `tools.runtimeTools[${i}].config`;
3403
3639
  const rtConfig = runtimeTool.config;
3404
3640
  if (runtimeTool.toolType === "subagent" && typeof rtConfig.agentId === "string" && rtConfig.agentId.startsWith("agent_")) {
@@ -3459,226 +3695,693 @@ var AgentDriftError = class extends Error {
3459
3695
  this.name = "AgentDriftError";
3460
3696
  this.plan = plan;
3461
3697
  }
3462
- };
3463
- function parseRequestError2(err) {
3464
- if (!(err instanceof Error)) return { status: null, body: null };
3465
- const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3466
- if (!match) return { status: null, body: null };
3467
- try {
3468
- return { status: Number(match[1]), body: JSON.parse(match[2]) };
3469
- } catch {
3470
- return { status: Number(match[1]), body: null };
3698
+ };
3699
+ function parseRequestError3(err) {
3700
+ if (!(err instanceof Error)) return { status: null, body: null };
3701
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3702
+ if (!match) return { status: null, body: null };
3703
+ try {
3704
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3705
+ } catch {
3706
+ return { status: Number(match[1]), body: null };
3707
+ }
3708
+ }
3709
+ function toConflictError3(err) {
3710
+ const { status, body } = parseRequestError3(err);
3711
+ if (status !== 409 || !isPlainObject3(body)) return null;
3712
+ const code = body.code;
3713
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3714
+ return new AgentEnsureConflictError(
3715
+ body
3716
+ );
3717
+ }
3718
+ var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
3719
+ function memoFor3(client) {
3720
+ let memo = serverHashMemo3.get(client);
3721
+ if (!memo) {
3722
+ memo = /* @__PURE__ */ new Map();
3723
+ serverHashMemo3.set(client, memo);
3724
+ }
3725
+ return memo;
3726
+ }
3727
+ var AgentsNamespace = class {
3728
+ constructor(getClient) {
3729
+ this.getClient = getClient;
3730
+ }
3731
+ /**
3732
+ * Idempotently converge a definition onto the platform. Hash-first: probes
3733
+ * with a content hash, and only ships the full definition when the server
3734
+ * reports a miss (`definitionRequired`). Creates an immutable version
3735
+ * snapshot on every change; never deletes.
3736
+ */
3737
+ async ensure(definition, options = {}) {
3738
+ const client = this.getClient();
3739
+ const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3740
+ const passthrough = {
3741
+ ...onConflict ? { onConflict } : {},
3742
+ ...release ? { release } : {},
3743
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3744
+ };
3745
+ if (dryRun || expectNoChanges) {
3746
+ const plan = await this.request(client, {
3747
+ name: definition.name,
3748
+ definition,
3749
+ dryRun: true,
3750
+ ...passthrough
3751
+ });
3752
+ if (plan.result !== "plan") {
3753
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3754
+ }
3755
+ if (expectNoChanges && plan.changes !== "none") {
3756
+ throw new AgentDriftError(plan);
3757
+ }
3758
+ return plan;
3759
+ }
3760
+ const memo = memoFor3(client);
3761
+ const localHash = await computeAgentContentHash({
3762
+ ...definition,
3763
+ config: definition.config
3764
+ });
3765
+ const memoKey = `${definition.name}\0${localHash}`;
3766
+ const contentHash = memo.get(memoKey) ?? localHash;
3767
+ const probe = await this.request(client, {
3768
+ name: definition.name,
3769
+ contentHash,
3770
+ ...passthrough
3771
+ });
3772
+ if (probe.result !== "definitionRequired") {
3773
+ this.memoize(memo, memoKey, probe);
3774
+ return probe;
3775
+ }
3776
+ const converged = await this.request(client, {
3777
+ name: definition.name,
3778
+ definition,
3779
+ ...passthrough
3780
+ });
3781
+ if (converged.result === "definitionRequired") {
3782
+ throw new Error("Server reported definitionRequired for a full-definition request");
3783
+ }
3784
+ this.memoize(memo, memoKey, converged);
3785
+ return converged;
3786
+ }
3787
+ /**
3788
+ * Pull the canonical definition + provenance for an agent by name — the
3789
+ * absorb-drift direction. The contentHash reflects the live agent state.
3790
+ */
3791
+ async pull(name) {
3792
+ const client = this.getClient();
3793
+ return client.get("/agents/pull", { name });
3794
+ }
3795
+ memoize(memo, memoKey, result) {
3796
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3797
+ }
3798
+ async request(client, body) {
3799
+ try {
3800
+ return await client.post(
3801
+ "/agents/ensure",
3802
+ body
3803
+ );
3804
+ } catch (err) {
3805
+ const conflict = toConflictError3(err);
3806
+ if (conflict) throw conflict;
3807
+ throw err;
3808
+ }
3809
+ }
3810
+ };
3811
+
3812
+ // src/tools-ensure.ts
3813
+ function isPlainObject4(value) {
3814
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3815
+ }
3816
+ function normalizeValue3(value) {
3817
+ if (Array.isArray(value)) {
3818
+ return value.map((item) => normalizeValue3(item));
3819
+ }
3820
+ if (isPlainObject4(value)) {
3821
+ const normalized = {};
3822
+ for (const key of Object.keys(value).sort()) {
3823
+ const entry = value[key];
3824
+ if (entry === void 0 || entry === null) continue;
3825
+ normalized[key] = normalizeValue3(entry);
3826
+ }
3827
+ return normalized;
3828
+ }
3829
+ return value;
3830
+ }
3831
+ function normalizeToolDefinition(definition) {
3832
+ const parametersSchema = isPlainObject4(definition.parametersSchema) ? normalizeValue3(definition.parametersSchema) : {};
3833
+ const config = isPlainObject4(definition.config) ? normalizeValue3(definition.config) : {};
3834
+ return {
3835
+ toolType: definition.toolType,
3836
+ ...definition.description ? { description: definition.description } : {},
3837
+ parametersSchema,
3838
+ config
3839
+ };
3840
+ }
3841
+ async function computeToolContentHash(definition) {
3842
+ const serialized = JSON.stringify(normalizeToolDefinition(definition));
3843
+ const encoded = new TextEncoder().encode(serialized);
3844
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3845
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3846
+ }
3847
+ var DEFINE_TOOL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
3848
+ "name",
3849
+ "description",
3850
+ "toolType",
3851
+ "parametersSchema",
3852
+ "config"
3853
+ ]);
3854
+ var TOOL_DEFINITION_TYPES = /* @__PURE__ */ new Set([
3855
+ "flow",
3856
+ "custom",
3857
+ "external",
3858
+ "graphql",
3859
+ "mcp",
3860
+ "local",
3861
+ "subagent"
3862
+ ]);
3863
+ function defineTool(input) {
3864
+ if (!input || typeof input !== "object") {
3865
+ throw new Error("defineTool requires a definition object");
3866
+ }
3867
+ if (typeof input.name !== "string" || input.name.length === 0) {
3868
+ throw new Error('defineTool requires a non-empty string "name"');
3869
+ }
3870
+ if (typeof input.description !== "string" || input.description.length === 0) {
3871
+ throw new Error('defineTool requires a non-empty string "description"');
3872
+ }
3873
+ if (typeof input.toolType !== "string" || !TOOL_DEFINITION_TYPES.has(input.toolType)) {
3874
+ throw new Error(
3875
+ `defineTool requires "toolType" to be one of: ${[...TOOL_DEFINITION_TYPES].join(", ")}`
3876
+ );
3877
+ }
3878
+ if (!isPlainObject4(input.parametersSchema)) {
3879
+ throw new Error('defineTool requires a "parametersSchema" object (a JSON Schema)');
3880
+ }
3881
+ if (!isPlainObject4(input.config)) {
3882
+ throw new Error('defineTool requires a "config" object');
3883
+ }
3884
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOOL_TOP_LEVEL_KEYS.has(key));
3885
+ if (unknownKeys.length > 0) {
3886
+ throw new Error(
3887
+ `defineTool: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, toolType, parametersSchema, config.`
3888
+ );
3889
+ }
3890
+ return {
3891
+ name: input.name,
3892
+ description: input.description,
3893
+ toolType: input.toolType,
3894
+ parametersSchema: input.parametersSchema,
3895
+ config: input.config
3896
+ };
3897
+ }
3898
+ var ToolEnsureConflictError = class extends Error {
3899
+ constructor(body) {
3900
+ super(body.error ?? `Tool ensure conflict: ${body.code}`);
3901
+ this.name = "ToolEnsureConflictError";
3902
+ this.code = body.code;
3903
+ this.lastModifiedSource = body.lastModifiedSource;
3904
+ this.modifiedAt = body.modifiedAt;
3905
+ this.currentHash = body.currentHash;
3906
+ }
3907
+ };
3908
+ var ToolDriftError = class extends Error {
3909
+ constructor(plan) {
3910
+ super(
3911
+ `Tool "${plan.toolId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.tools.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
3912
+ );
3913
+ this.name = "ToolDriftError";
3914
+ this.plan = plan;
3915
+ }
3916
+ };
3917
+ function parseRequestError4(err) {
3918
+ if (!(err instanceof Error)) return { status: null, body: null };
3919
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3920
+ if (!match) return { status: null, body: null };
3921
+ try {
3922
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
3923
+ } catch {
3924
+ return { status: Number(match[1]), body: null };
3925
+ }
3926
+ }
3927
+ function toConflictError4(err) {
3928
+ const { status, body } = parseRequestError4(err);
3929
+ if (status !== 409 || !isPlainObject4(body)) return null;
3930
+ const code = body.code;
3931
+ if (code !== "external_modification" && code !== "remote_changed") return null;
3932
+ return new ToolEnsureConflictError(
3933
+ body
3934
+ );
3935
+ }
3936
+ var serverHashMemo4 = /* @__PURE__ */ new WeakMap();
3937
+ function memoFor4(client) {
3938
+ let memo = serverHashMemo4.get(client);
3939
+ if (!memo) {
3940
+ memo = /* @__PURE__ */ new Map();
3941
+ serverHashMemo4.set(client, memo);
3942
+ }
3943
+ return memo;
3944
+ }
3945
+ function memoize3(memo, memoKey, result) {
3946
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3947
+ }
3948
+ async function request3(client, body) {
3949
+ try {
3950
+ return await client.post(
3951
+ "/tools/ensure",
3952
+ body
3953
+ );
3954
+ } catch (err) {
3955
+ const conflict = toConflictError4(err);
3956
+ if (conflict) throw conflict;
3957
+ throw err;
3958
+ }
3959
+ }
3960
+ async function ensureTool(client, definition, options = {}) {
3961
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
3962
+ const passthrough = {
3963
+ ...onConflict ? { onConflict } : {},
3964
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
3965
+ };
3966
+ if (dryRun || expectNoChanges) {
3967
+ const plan = await request3(client, {
3968
+ name: definition.name,
3969
+ definition,
3970
+ dryRun: true,
3971
+ ...passthrough
3972
+ });
3973
+ if (plan.result !== "plan") {
3974
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3975
+ }
3976
+ if (expectNoChanges && plan.changes !== "none") {
3977
+ throw new ToolDriftError(plan);
3978
+ }
3979
+ return plan;
3980
+ }
3981
+ const memo = memoFor4(client);
3982
+ const localHash = await computeToolContentHash(definition);
3983
+ const memoKey = `${definition.name} ${localHash}`;
3984
+ const contentHash = memo.get(memoKey) ?? localHash;
3985
+ const probe = await request3(client, {
3986
+ name: definition.name,
3987
+ contentHash,
3988
+ ...passthrough
3989
+ });
3990
+ if (probe.result !== "definitionRequired") {
3991
+ memoize3(memo, memoKey, probe);
3992
+ return probe;
3993
+ }
3994
+ const converged = await request3(client, {
3995
+ name: definition.name,
3996
+ definition,
3997
+ ...passthrough
3998
+ });
3999
+ if (converged.result === "definitionRequired") {
4000
+ throw new Error("Server reported definitionRequired for a full-definition request");
4001
+ }
4002
+ memoize3(memo, memoKey, converged);
4003
+ return converged;
4004
+ }
4005
+ async function pullTool(client, name) {
4006
+ return client.get("/tools/pull", { name });
4007
+ }
4008
+
4009
+ // src/tools-namespace.ts
4010
+ var ToolsNamespace = class {
4011
+ constructor(getClient) {
4012
+ this.getClient = getClient;
4013
+ }
4014
+ /**
4015
+ * Idempotently converge a `defineTool` definition onto the platform.
4016
+ * Hash-first: the steady state is one tiny probe request. Creates or updates
4017
+ * the saved tool; never deletes. Identity is name + account scope.
4018
+ *
4019
+ * @example
4020
+ * ```typescript
4021
+ * const weather = defineTool({
4022
+ * name: 'Weather Lookup',
4023
+ * description: 'Fetch the current weather for a city',
4024
+ * toolType: 'external',
4025
+ * parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
4026
+ * config: { url: 'https://api.example.com/weather', method: 'GET' },
4027
+ * })
4028
+ *
4029
+ * // Converge (CI/deploy).
4030
+ * const result = await Runtype.tools.ensure(weather)
4031
+ *
4032
+ * // PR drift gate.
4033
+ * await Runtype.tools.ensure(weather, { expectNoChanges: true })
4034
+ * ```
4035
+ */
4036
+ async ensure(definition, options = {}) {
4037
+ return ensureTool(this.getClient(), definition, options);
4038
+ }
4039
+ /**
4040
+ * Pull the canonical definition + provenance for a tool by name — the
4041
+ * absorb-drift direction of the ensure protocol.
4042
+ */
4043
+ async pull(name) {
4044
+ return pullTool(this.getClient(), name);
4045
+ }
4046
+ };
4047
+
4048
+ // src/products-ensure.ts
4049
+ function isPlainObject5(value) {
4050
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4051
+ }
4052
+ function normalizeValue4(value) {
4053
+ if (Array.isArray(value)) {
4054
+ return value.map((item) => normalizeValue4(item));
4055
+ }
4056
+ if (isPlainObject5(value)) {
4057
+ const normalized = {};
4058
+ for (const key of Object.keys(value).sort()) {
4059
+ const entry = value[key];
4060
+ if (entry === void 0 || entry === null) continue;
4061
+ normalized[key] = normalizeValue4(entry);
4062
+ }
4063
+ return normalized;
4064
+ }
4065
+ return value;
4066
+ }
4067
+ function normalizeProductDefinition(definition) {
4068
+ const spec = isPlainObject5(definition.spec) ? normalizeValue4(definition.spec) : {};
4069
+ return {
4070
+ ...definition.description ? { description: definition.description } : {},
4071
+ ...definition.icon ? { icon: definition.icon } : {},
4072
+ spec
4073
+ };
4074
+ }
4075
+ async function computeProductContentHash(definition) {
4076
+ const serialized = JSON.stringify(normalizeProductDefinition(definition));
4077
+ const encoded = new TextEncoder().encode(serialized);
4078
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
4079
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
4080
+ }
4081
+ var DEFINE_PRODUCT_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["name", "description", "icon", "spec"]);
4082
+ function defineProduct(input) {
4083
+ if (!input || typeof input !== "object") {
4084
+ throw new Error("defineProduct requires a definition object");
4085
+ }
4086
+ if (typeof input.name !== "string" || input.name.length === 0) {
4087
+ throw new Error('defineProduct requires a non-empty string "name"');
4088
+ }
4089
+ if (input.description != null && typeof input.description !== "string") {
4090
+ throw new Error('defineProduct "description" must be a string when provided');
4091
+ }
4092
+ if (input.icon != null && typeof input.icon !== "string") {
4093
+ throw new Error('defineProduct "icon" must be a string when provided');
4094
+ }
4095
+ if (input.spec != null && !isPlainObject5(input.spec)) {
4096
+ throw new Error('defineProduct "spec" must be an object when provided');
4097
+ }
4098
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_PRODUCT_TOP_LEVEL_KEYS.has(key));
4099
+ if (unknownKeys.length > 0) {
4100
+ throw new Error(
4101
+ `defineProduct: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, icon, spec. (canvas / nested capabilities and surfaces are not converged by ensure.)`
4102
+ );
4103
+ }
4104
+ return {
4105
+ name: input.name,
4106
+ ...input.description !== void 0 ? { description: input.description } : {},
4107
+ ...input.icon !== void 0 ? { icon: input.icon } : {},
4108
+ ...input.spec !== void 0 ? { spec: input.spec } : {}
4109
+ };
4110
+ }
4111
+ var ProductEnsureConflictError = class extends Error {
4112
+ constructor(body) {
4113
+ super(body.error ?? `Product ensure conflict: ${body.code}`);
4114
+ this.name = "ProductEnsureConflictError";
4115
+ this.code = body.code;
4116
+ this.lastModifiedSource = body.lastModifiedSource;
4117
+ this.modifiedAt = body.modifiedAt;
4118
+ this.currentHash = body.currentHash;
4119
+ }
4120
+ };
4121
+ var ProductDriftError = class extends Error {
4122
+ constructor(plan) {
4123
+ super(
4124
+ `Product "${plan.productId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.products.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
4125
+ );
4126
+ this.name = "ProductDriftError";
4127
+ this.plan = plan;
4128
+ }
4129
+ };
4130
+ function parseRequestError5(err) {
4131
+ if (!(err instanceof Error)) return { status: null, body: null };
4132
+ const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
4133
+ if (!match) return { status: null, body: null };
4134
+ try {
4135
+ return { status: Number(match[1]), body: JSON.parse(match[2]) };
4136
+ } catch {
4137
+ return { status: Number(match[1]), body: null };
4138
+ }
4139
+ }
4140
+ function toConflictError5(err) {
4141
+ const { status, body } = parseRequestError5(err);
4142
+ if (status !== 409 || !isPlainObject5(body)) return null;
4143
+ const code = body.code;
4144
+ if (code !== "external_modification" && code !== "remote_changed") return null;
4145
+ return new ProductEnsureConflictError(
4146
+ body
4147
+ );
4148
+ }
4149
+ var serverHashMemo5 = /* @__PURE__ */ new WeakMap();
4150
+ function memoFor5(client) {
4151
+ let memo = serverHashMemo5.get(client);
4152
+ if (!memo) {
4153
+ memo = /* @__PURE__ */ new Map();
4154
+ serverHashMemo5.set(client, memo);
4155
+ }
4156
+ return memo;
4157
+ }
4158
+ function memoize4(memo, memoKey, result) {
4159
+ if (result.result !== "plan") memo.set(memoKey, result.contentHash);
4160
+ }
4161
+ async function request4(client, body) {
4162
+ try {
4163
+ return await client.post(
4164
+ "/products/ensure",
4165
+ body
4166
+ );
4167
+ } catch (err) {
4168
+ const conflict = toConflictError5(err);
4169
+ if (conflict) throw conflict;
4170
+ throw err;
4171
+ }
4172
+ }
4173
+ async function ensureProduct(client, definition, options = {}) {
4174
+ const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
4175
+ const passthrough = {
4176
+ ...onConflict ? { onConflict } : {},
4177
+ ...expectedRemoteHash ? { expectedRemoteHash } : {}
4178
+ };
4179
+ if (dryRun || expectNoChanges) {
4180
+ const plan = await request4(client, {
4181
+ name: definition.name,
4182
+ definition,
4183
+ dryRun: true,
4184
+ ...passthrough
4185
+ });
4186
+ if (plan.result !== "plan") {
4187
+ throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
4188
+ }
4189
+ if (expectNoChanges && plan.changes !== "none") {
4190
+ throw new ProductDriftError(plan);
4191
+ }
4192
+ return plan;
4193
+ }
4194
+ const memo = memoFor5(client);
4195
+ const localHash = await computeProductContentHash(definition);
4196
+ const memoKey = `${definition.name} ${localHash}`;
4197
+ const contentHash = memo.get(memoKey) ?? localHash;
4198
+ const probe = await request4(client, {
4199
+ name: definition.name,
4200
+ contentHash,
4201
+ ...passthrough
4202
+ });
4203
+ if (probe.result !== "definitionRequired") {
4204
+ memoize4(memo, memoKey, probe);
4205
+ return probe;
4206
+ }
4207
+ const converged = await request4(client, {
4208
+ name: definition.name,
4209
+ definition,
4210
+ ...passthrough
4211
+ });
4212
+ if (converged.result === "definitionRequired") {
4213
+ throw new Error("Server reported definitionRequired for a full-definition request");
3471
4214
  }
4215
+ memoize4(memo, memoKey, converged);
4216
+ return converged;
3472
4217
  }
3473
- function toConflictError2(err) {
3474
- const { status, body } = parseRequestError2(err);
3475
- if (status !== 409 || !isPlainObject2(body)) return null;
3476
- const code = body.code;
3477
- if (code !== "external_modification" && code !== "remote_changed") return null;
3478
- return new AgentEnsureConflictError(
3479
- body
3480
- );
3481
- }
3482
- var serverHashMemo2 = /* @__PURE__ */ new WeakMap();
3483
- function memoFor2(client) {
3484
- let memo = serverHashMemo2.get(client);
3485
- if (!memo) {
3486
- memo = /* @__PURE__ */ new Map();
3487
- serverHashMemo2.set(client, memo);
3488
- }
3489
- return memo;
4218
+ async function pullProduct(client, name) {
4219
+ return client.get("/products/pull", { name });
3490
4220
  }
3491
- var AgentsNamespace = class {
4221
+
4222
+ // src/products-namespace.ts
4223
+ var ProductsNamespace = class {
3492
4224
  constructor(getClient) {
3493
4225
  this.getClient = getClient;
3494
4226
  }
3495
4227
  /**
3496
- * Idempotently converge a definition onto the platform. Hash-first: probes
3497
- * with a content hash, and only ships the full definition when the server
3498
- * reports a miss (`definitionRequired`). Creates an immutable version
3499
- * snapshot on every change; never deletes.
4228
+ * Idempotently converge a `defineProduct` definition onto the platform.
4229
+ * Hash-first: the steady state is one tiny probe request. Creates or updates
4230
+ * the top-level product record; never deletes. Identity is name + account
4231
+ * scope.
4232
+ *
4233
+ * @example
4234
+ * ```typescript
4235
+ * const product = defineProduct({
4236
+ * name: 'Support Copilot',
4237
+ * description: 'An AI support assistant',
4238
+ * icon: '🤖',
4239
+ * spec: { productGoal: 'Deflect tier-1 tickets', productStage: 'beta' },
4240
+ * })
4241
+ *
4242
+ * // Converge (CI/deploy).
4243
+ * const result = await Runtype.products.ensure(product)
4244
+ *
4245
+ * // PR drift gate.
4246
+ * await Runtype.products.ensure(product, { expectNoChanges: true })
4247
+ * ```
3500
4248
  */
3501
4249
  async ensure(definition, options = {}) {
3502
- const client = this.getClient();
3503
- const { dryRun, onConflict, release, expectedRemoteHash, expectNoChanges } = options;
3504
- const passthrough = {
3505
- ...onConflict ? { onConflict } : {},
3506
- ...release ? { release } : {},
3507
- ...expectedRemoteHash ? { expectedRemoteHash } : {}
3508
- };
3509
- if (dryRun || expectNoChanges) {
3510
- const plan = await this.request(client, {
3511
- name: definition.name,
3512
- definition,
3513
- dryRun: true,
3514
- ...passthrough
3515
- });
3516
- if (plan.result !== "plan") {
3517
- throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3518
- }
3519
- if (expectNoChanges && plan.changes !== "none") {
3520
- throw new AgentDriftError(plan);
3521
- }
3522
- return plan;
3523
- }
3524
- const memo = memoFor2(client);
3525
- const localHash = await computeAgentContentHash({
3526
- ...definition,
3527
- config: definition.config
3528
- });
3529
- const memoKey = `${definition.name}\0${localHash}`;
3530
- const contentHash = memo.get(memoKey) ?? localHash;
3531
- const probe = await this.request(client, {
3532
- name: definition.name,
3533
- contentHash,
3534
- ...passthrough
3535
- });
3536
- if (probe.result !== "definitionRequired") {
3537
- this.memoize(memo, memoKey, probe);
3538
- return probe;
3539
- }
3540
- const converged = await this.request(client, {
3541
- name: definition.name,
3542
- definition,
3543
- ...passthrough
3544
- });
3545
- if (converged.result === "definitionRequired") {
3546
- throw new Error("Server reported definitionRequired for a full-definition request");
3547
- }
3548
- this.memoize(memo, memoKey, converged);
3549
- return converged;
4250
+ return ensureProduct(this.getClient(), definition, options);
3550
4251
  }
3551
4252
  /**
3552
- * Pull the canonical definition + provenance for an agent by name — the
3553
- * absorb-drift direction. The contentHash reflects the live agent state.
4253
+ * Pull the canonical definition + provenance for a product by name — the
4254
+ * absorb-drift direction of the ensure protocol.
3554
4255
  */
3555
4256
  async pull(name) {
3556
- const client = this.getClient();
3557
- return client.get("/agents/pull", { name });
3558
- }
3559
- memoize(memo, memoKey, result) {
3560
- if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3561
- }
3562
- async request(client, body) {
3563
- try {
3564
- return await client.post(
3565
- "/agents/ensure",
3566
- body
3567
- );
3568
- } catch (err) {
3569
- const conflict = toConflictError2(err);
3570
- if (conflict) throw conflict;
3571
- throw err;
3572
- }
4257
+ return pullProduct(this.getClient(), name);
3573
4258
  }
3574
4259
  };
3575
4260
 
3576
- // src/tools-ensure.ts
3577
- function isPlainObject3(value) {
4261
+ // src/surfaces-ensure.ts
4262
+ function isPlainObject6(value) {
3578
4263
  return value !== null && typeof value === "object" && !Array.isArray(value);
3579
4264
  }
3580
- function normalizeValue2(value) {
4265
+ function normalizeValue5(value) {
3581
4266
  if (Array.isArray(value)) {
3582
- return value.map((item) => normalizeValue2(item));
4267
+ return value.map((item) => normalizeValue5(item));
3583
4268
  }
3584
- if (isPlainObject3(value)) {
4269
+ if (isPlainObject6(value)) {
3585
4270
  const normalized = {};
3586
4271
  for (const key of Object.keys(value).sort()) {
3587
4272
  const entry = value[key];
3588
4273
  if (entry === void 0 || entry === null) continue;
3589
- normalized[key] = normalizeValue2(entry);
4274
+ normalized[key] = normalizeValue5(entry);
3590
4275
  }
3591
4276
  return normalized;
3592
4277
  }
3593
4278
  return value;
3594
4279
  }
3595
- function normalizeToolDefinition(definition) {
3596
- const parametersSchema = isPlainObject3(definition.parametersSchema) ? normalizeValue2(definition.parametersSchema) : {};
3597
- const config = isPlainObject3(definition.config) ? normalizeValue2(definition.config) : {};
4280
+ function normalizeSurfaceDefinition(definition) {
4281
+ const behavior = isPlainObject6(definition.behavior) ? normalizeValue5(definition.behavior) : { type: definition.type };
3598
4282
  return {
3599
- toolType: definition.toolType,
3600
- ...definition.description ? { description: definition.description } : {},
3601
- parametersSchema,
3602
- config
4283
+ type: definition.type,
4284
+ behavior,
4285
+ status: definition.status || "draft",
4286
+ environment: definition.environment || "development"
3603
4287
  };
3604
4288
  }
3605
- async function computeToolContentHash(definition) {
3606
- const serialized = JSON.stringify(normalizeToolDefinition(definition));
4289
+ async function computeSurfaceContentHash(definition) {
4290
+ const serialized = JSON.stringify(normalizeSurfaceDefinition(definition));
3607
4291
  const encoded = new TextEncoder().encode(serialized);
3608
4292
  const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
3609
4293
  return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
3610
4294
  }
3611
- var DEFINE_TOOL_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
4295
+ var DEFINE_SURFACE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
3612
4296
  "name",
3613
- "description",
3614
- "toolType",
3615
- "parametersSchema",
3616
- "config"
4297
+ "type",
4298
+ "behavior",
4299
+ "inbound",
4300
+ "outbound",
4301
+ "status",
4302
+ "environment"
3617
4303
  ]);
3618
- var TOOL_DEFINITION_TYPES = /* @__PURE__ */ new Set([
3619
- "flow",
3620
- "custom",
3621
- "external",
3622
- "graphql",
4304
+ var SURFACE_DEFINITION_TYPES = /* @__PURE__ */ new Set([
4305
+ "chat",
3623
4306
  "mcp",
3624
- "local",
3625
- "subagent"
4307
+ "mcp_code",
4308
+ "api",
4309
+ "webhook",
4310
+ "schedule",
4311
+ "a2a",
4312
+ "email",
4313
+ "slack",
4314
+ "sms",
4315
+ "imessage",
4316
+ "discord",
4317
+ "whatsapp",
4318
+ "telegram",
4319
+ "hosted-page",
4320
+ "chrome_extension"
3626
4321
  ]);
3627
- function defineTool(input) {
4322
+ function defineSurface(input) {
3628
4323
  if (!input || typeof input !== "object") {
3629
- throw new Error("defineTool requires a definition object");
4324
+ throw new Error("defineSurface requires a definition object");
3630
4325
  }
3631
4326
  if (typeof input.name !== "string" || input.name.length === 0) {
3632
- throw new Error('defineTool requires a non-empty string "name"');
4327
+ throw new Error('defineSurface requires a non-empty string "name"');
3633
4328
  }
3634
- if (typeof input.description !== "string" || input.description.length === 0) {
3635
- throw new Error('defineTool requires a non-empty string "description"');
3636
- }
3637
- if (typeof input.toolType !== "string" || !TOOL_DEFINITION_TYPES.has(input.toolType)) {
4329
+ if (typeof input.type !== "string" || !SURFACE_DEFINITION_TYPES.has(input.type)) {
3638
4330
  throw new Error(
3639
- `defineTool requires "toolType" to be one of: ${[...TOOL_DEFINITION_TYPES].join(", ")}`
4331
+ `defineSurface requires "type" to be one of: ${[...SURFACE_DEFINITION_TYPES].join(", ")}`
3640
4332
  );
3641
4333
  }
3642
- if (!isPlainObject3(input.parametersSchema)) {
3643
- throw new Error('defineTool requires a "parametersSchema" object (a JSON Schema)');
4334
+ if (input.behavior !== void 0 && !isPlainObject6(input.behavior)) {
4335
+ throw new Error('defineSurface "behavior" must be an object when provided');
3644
4336
  }
3645
- if (!isPlainObject3(input.config)) {
3646
- throw new Error('defineTool requires a "config" object');
4337
+ if (input.inbound !== void 0 && !isPlainObject6(input.inbound)) {
4338
+ throw new Error('defineSurface "inbound" must be an object when provided');
3647
4339
  }
3648
- const unknownKeys = Object.keys(input).filter((key) => !DEFINE_TOOL_TOP_LEVEL_KEYS.has(key));
4340
+ if (input.outbound !== void 0 && !isPlainObject6(input.outbound)) {
4341
+ throw new Error('defineSurface "outbound" must be an object when provided');
4342
+ }
4343
+ if (input.status !== void 0 && !["draft", "active", "paused"].includes(input.status)) {
4344
+ throw new Error('defineSurface "status" must be one of: draft, active, paused');
4345
+ }
4346
+ if (input.environment !== void 0 && !["production", "development"].includes(input.environment)) {
4347
+ throw new Error('defineSurface "environment" must be one of: production, development');
4348
+ }
4349
+ const unknownKeys = Object.keys(input).filter((key) => !DEFINE_SURFACE_TOP_LEVEL_KEYS.has(key));
3649
4350
  if (unknownKeys.length > 0) {
3650
4351
  throw new Error(
3651
- `defineTool: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, description, toolType, parametersSchema, config.`
4352
+ `defineSurface: unknown field(s): ${unknownKeys.join(", ")}. Allowed fields are name, type, behavior, inbound, outbound, status, environment.`
3652
4353
  );
3653
4354
  }
3654
4355
  return {
3655
4356
  name: input.name,
3656
- description: input.description,
3657
- toolType: input.toolType,
3658
- parametersSchema: input.parametersSchema,
3659
- config: input.config
4357
+ type: input.type,
4358
+ ...input.behavior !== void 0 ? { behavior: input.behavior } : {},
4359
+ ...input.inbound !== void 0 ? { inbound: input.inbound } : {},
4360
+ ...input.outbound !== void 0 ? { outbound: input.outbound } : {},
4361
+ ...input.status !== void 0 ? { status: input.status } : {},
4362
+ ...input.environment !== void 0 ? { environment: input.environment } : {}
3660
4363
  };
3661
4364
  }
3662
- var ToolEnsureConflictError = class extends Error {
4365
+ var SurfaceEnsureConflictError = class extends Error {
3663
4366
  constructor(body) {
3664
- super(body.error ?? `Tool ensure conflict: ${body.code}`);
3665
- this.name = "ToolEnsureConflictError";
4367
+ super(body.error ?? `Surface ensure conflict: ${body.code}`);
4368
+ this.name = "SurfaceEnsureConflictError";
3666
4369
  this.code = body.code;
3667
4370
  this.lastModifiedSource = body.lastModifiedSource;
3668
4371
  this.modifiedAt = body.modifiedAt;
3669
4372
  this.currentHash = body.currentHash;
3670
4373
  }
3671
4374
  };
3672
- var ToolDriftError = class extends Error {
4375
+ var SurfaceDriftError = class extends Error {
3673
4376
  constructor(plan) {
3674
4377
  super(
3675
- `Tool "${plan.toolId ?? "definition"}" drifted: plan is '${plan.changes}' (changed: ${plan.changedKeys.join(", ") || "n/a"}). Run client.tools.pull(name) to absorb the remote edit into your repo, or re-run ensure to converge.`
4378
+ `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.`
3676
4379
  );
3677
- this.name = "ToolDriftError";
4380
+ this.name = "SurfaceDriftError";
3678
4381
  this.plan = plan;
3679
4382
  }
3680
4383
  };
3681
- function parseRequestError3(err) {
4384
+ function parseRequestError6(err) {
3682
4385
  if (!(err instanceof Error)) return { status: null, body: null };
3683
4386
  const match = err.message.match(/^API request failed: (\d{3}) .*? - ([\s\S]*)$/);
3684
4387
  if (!match) return { status: null, body: null };
@@ -3688,47 +4391,47 @@ function parseRequestError3(err) {
3688
4391
  return { status: Number(match[1]), body: null };
3689
4392
  }
3690
4393
  }
3691
- function toConflictError3(err) {
3692
- const { status, body } = parseRequestError3(err);
3693
- if (status !== 409 || !isPlainObject3(body)) return null;
4394
+ function toConflictError6(err) {
4395
+ const { status, body } = parseRequestError6(err);
4396
+ if (status !== 409 || !isPlainObject6(body)) return null;
3694
4397
  const code = body.code;
3695
4398
  if (code !== "external_modification" && code !== "remote_changed") return null;
3696
- return new ToolEnsureConflictError(
4399
+ return new SurfaceEnsureConflictError(
3697
4400
  body
3698
4401
  );
3699
4402
  }
3700
- var serverHashMemo3 = /* @__PURE__ */ new WeakMap();
3701
- function memoFor3(client) {
3702
- let memo = serverHashMemo3.get(client);
4403
+ var serverHashMemo6 = /* @__PURE__ */ new WeakMap();
4404
+ function memoFor6(client) {
4405
+ let memo = serverHashMemo6.get(client);
3703
4406
  if (!memo) {
3704
4407
  memo = /* @__PURE__ */ new Map();
3705
- serverHashMemo3.set(client, memo);
4408
+ serverHashMemo6.set(client, memo);
3706
4409
  }
3707
4410
  return memo;
3708
4411
  }
3709
- function memoize2(memo, memoKey, result) {
4412
+ function memoize5(memo, memoKey, result) {
3710
4413
  if (result.result !== "plan") memo.set(memoKey, result.contentHash);
3711
4414
  }
3712
- async function request2(client, body) {
4415
+ async function request5(client, productId, body) {
3713
4416
  try {
3714
4417
  return await client.post(
3715
- "/tools/ensure",
4418
+ `/products/${encodeURIComponent(productId)}/surfaces/ensure`,
3716
4419
  body
3717
4420
  );
3718
4421
  } catch (err) {
3719
- const conflict = toConflictError3(err);
4422
+ const conflict = toConflictError6(err);
3720
4423
  if (conflict) throw conflict;
3721
4424
  throw err;
3722
4425
  }
3723
4426
  }
3724
- async function ensureTool(client, definition, options = {}) {
4427
+ async function ensureSurface(client, productId, definition, options = {}) {
3725
4428
  const { dryRun, onConflict, expectedRemoteHash, expectNoChanges } = options;
3726
4429
  const passthrough = {
3727
4430
  ...onConflict ? { onConflict } : {},
3728
4431
  ...expectedRemoteHash ? { expectedRemoteHash } : {}
3729
4432
  };
3730
4433
  if (dryRun || expectNoChanges) {
3731
- const plan = await request2(client, {
4434
+ const plan = await request5(client, productId, {
3732
4435
  name: definition.name,
3733
4436
  definition,
3734
4437
  dryRun: true,
@@ -3738,24 +4441,24 @@ async function ensureTool(client, definition, options = {}) {
3738
4441
  throw new Error(`Expected a plan result from dryRun, got '${plan.result}'`);
3739
4442
  }
3740
4443
  if (expectNoChanges && plan.changes !== "none") {
3741
- throw new ToolDriftError(plan);
4444
+ throw new SurfaceDriftError(plan);
3742
4445
  }
3743
4446
  return plan;
3744
4447
  }
3745
- const memo = memoFor3(client);
3746
- const localHash = await computeToolContentHash(definition);
3747
- const memoKey = `${definition.name} ${localHash}`;
4448
+ const memo = memoFor6(client);
4449
+ const localHash = await computeSurfaceContentHash(definition);
4450
+ const memoKey = `${productId} ${definition.name} ${localHash}`;
3748
4451
  const contentHash = memo.get(memoKey) ?? localHash;
3749
- const probe = await request2(client, {
4452
+ const probe = await request5(client, productId, {
3750
4453
  name: definition.name,
3751
4454
  contentHash,
3752
4455
  ...passthrough
3753
4456
  });
3754
4457
  if (probe.result !== "definitionRequired") {
3755
- memoize2(memo, memoKey, probe);
4458
+ memoize5(memo, memoKey, probe);
3756
4459
  return probe;
3757
4460
  }
3758
- const converged = await request2(client, {
4461
+ const converged = await request5(client, productId, {
3759
4462
  name: definition.name,
3760
4463
  definition,
3761
4464
  ...passthrough
@@ -3763,49 +4466,51 @@ async function ensureTool(client, definition, options = {}) {
3763
4466
  if (converged.result === "definitionRequired") {
3764
4467
  throw new Error("Server reported definitionRequired for a full-definition request");
3765
4468
  }
3766
- memoize2(memo, memoKey, converged);
4469
+ memoize5(memo, memoKey, converged);
3767
4470
  return converged;
3768
4471
  }
3769
- async function pullTool(client, name) {
3770
- return client.get("/tools/pull", { name });
4472
+ async function pullSurface(client, productId, name) {
4473
+ return client.get(
4474
+ `/products/${encodeURIComponent(productId)}/surfaces/pull`,
4475
+ { name }
4476
+ );
3771
4477
  }
3772
4478
 
3773
- // src/tools-namespace.ts
3774
- var ToolsNamespace = class {
4479
+ // src/surfaces-namespace.ts
4480
+ var SurfacesNamespace = class {
3775
4481
  constructor(getClient) {
3776
4482
  this.getClient = getClient;
3777
4483
  }
3778
4484
  /**
3779
- * Idempotently converge a `defineTool` definition onto the platform.
4485
+ * Idempotently converge a `defineSurface` definition onto a product.
3780
4486
  * Hash-first: the steady state is one tiny probe request. Creates or updates
3781
- * the saved tool; never deletes. Identity is name + account scope.
4487
+ * the surface; never deletes. Identity is name + product.
3782
4488
  *
3783
4489
  * @example
3784
4490
  * ```typescript
3785
- * const weather = defineTool({
3786
- * name: 'Weather Lookup',
3787
- * description: 'Fetch the current weather for a city',
3788
- * toolType: 'external',
3789
- * parametersSchema: { type: 'object', properties: { city: { type: 'string' } } },
3790
- * config: { url: 'https://api.example.com/weather', method: 'GET' },
4491
+ * const chat = defineSurface({
4492
+ * name: 'Support Chat',
4493
+ * type: 'chat',
4494
+ * behavior: { type: 'chat', greeting: 'Hi there!' },
4495
+ * status: 'active',
3791
4496
  * })
3792
4497
  *
3793
4498
  * // Converge (CI/deploy).
3794
- * const result = await Runtype.tools.ensure(weather)
4499
+ * const result = await Runtype.surfaces.ensure('product_abc', chat)
3795
4500
  *
3796
4501
  * // PR drift gate.
3797
- * await Runtype.tools.ensure(weather, { expectNoChanges: true })
4502
+ * await Runtype.surfaces.ensure('product_abc', chat, { expectNoChanges: true })
3798
4503
  * ```
3799
4504
  */
3800
- async ensure(definition, options = {}) {
3801
- return ensureTool(this.getClient(), definition, options);
4505
+ async ensure(productId, definition, options = {}) {
4506
+ return ensureSurface(this.getClient(), productId, definition, options);
3802
4507
  }
3803
4508
  /**
3804
- * Pull the canonical definition + provenance for a tool by name the
3805
- * absorb-drift direction of the ensure protocol.
4509
+ * Pull the canonical definition + provenance for a surface by name within a
4510
+ * product — the absorb-drift direction of the ensure protocol.
3806
4511
  */
3807
- async pull(name) {
3808
- return pullTool(this.getClient(), name);
4512
+ async pull(productId, name) {
4513
+ return pullSurface(this.getClient(), productId, name);
3809
4514
  }
3810
4515
  };
3811
4516
 
@@ -4225,6 +4930,66 @@ var Runtype = class {
4225
4930
  static get tools() {
4226
4931
  return new ToolsNamespace(() => this.getClient());
4227
4932
  }
4933
+ /**
4934
+ * Products namespace - Product config-as-code (define / ensure / pull)
4935
+ *
4936
+ * Converges the top-level product record (description, icon, spec). Nested
4937
+ * capabilities/surfaces/tools and the canvas UI layout state are not
4938
+ * converged by ensure.
4939
+ *
4940
+ * @example
4941
+ * ```typescript
4942
+ * import { defineProduct, Runtype } from '@runtypelabs/sdk'
4943
+ *
4944
+ * const product = defineProduct({
4945
+ * name: 'Support Copilot',
4946
+ * description: 'An AI support assistant',
4947
+ * icon: '🤖',
4948
+ * spec: { productGoal: 'Deflect tier-1 tickets', productStage: 'beta' },
4949
+ * })
4950
+ *
4951
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
4952
+ * await Runtype.products.ensure(product)
4953
+ *
4954
+ * // CI drift gate
4955
+ * await Runtype.products.ensure(product, { expectNoChanges: true })
4956
+ *
4957
+ * // Absorb a dashboard edit back into the repo
4958
+ * const { definition } = await Runtype.products.pull('Support Copilot')
4959
+ * ```
4960
+ */
4961
+ static get products() {
4962
+ return new ProductsNamespace(() => this.getClient());
4963
+ }
4964
+ /**
4965
+ * Config-as-code operations for product surfaces. `surfaces.ensure` is the
4966
+ * deploy-time, non-executing converge (create-or-update a surface by name
4967
+ * within a product); `surfaces.pull` is the absorb-drift direction.
4968
+ *
4969
+ * @example
4970
+ * ```typescript
4971
+ * import { Runtype, defineSurface } from '@runtypelabs/sdk'
4972
+ *
4973
+ * const chat = defineSurface({
4974
+ * name: 'Support Chat',
4975
+ * type: 'chat',
4976
+ * behavior: { type: 'chat', greeting: 'Hi there!' },
4977
+ * status: 'active',
4978
+ * })
4979
+ *
4980
+ * // Converge at deploy time (idempotent; one tiny probe in steady state)
4981
+ * await Runtype.surfaces.ensure('product_abc', chat)
4982
+ *
4983
+ * // CI drift gate
4984
+ * await Runtype.surfaces.ensure('product_abc', chat, { expectNoChanges: true })
4985
+ *
4986
+ * // Absorb a dashboard edit back into the repo
4987
+ * const { definition } = await Runtype.surfaces.pull('product_abc', 'Support Chat')
4988
+ * ```
4989
+ */
4990
+ static get surfaces() {
4991
+ return new SurfacesNamespace(() => this.getClient());
4992
+ }
4228
4993
  };
4229
4994
 
4230
4995
  // src/generated-tool-gate.ts
@@ -4447,8 +5212,8 @@ function buildGeneratedRuntimeToolGateOutput(proposal, options = {}) {
4447
5212
  ...decision.tool ? { tool: decision.tool } : {}
4448
5213
  };
4449
5214
  }
4450
- function attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options = {}) {
4451
- const stepList = request3.flow.steps;
5215
+ function attachRuntimeToolsToDispatchRequest(request6, runtimeTools, options = {}) {
5216
+ const stepList = request6.flow.steps;
4452
5217
  if (!stepList || !Array.isArray(stepList) || stepList.length === 0) {
4453
5218
  throw new Error("Cannot attach runtime tools: dispatch request must include flow.steps");
4454
5219
  }
@@ -4491,9 +5256,9 @@ function attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options = {
4491
5256
  }
4492
5257
  };
4493
5258
  return {
4494
- ...request3,
5259
+ ...request6,
4495
5260
  flow: {
4496
- ...request3.flow,
5261
+ ...request6.flow,
4497
5262
  // `clonedSteps` is a structural clone of `request.flow.steps` (already
4498
5263
  // `FlowStepDefinition[]`); only the prompt step's `config.tools` was
4499
5264
  // merged, so every step's `type` discriminant is preserved. The clone is
@@ -4503,12 +5268,12 @@ function attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options = {
4503
5268
  }
4504
5269
  };
4505
5270
  }
4506
- function applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options = {}) {
5271
+ function applyGeneratedRuntimeToolProposalToDispatchRequest(request6, proposal, options = {}) {
4507
5272
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options.gate);
4508
5273
  if (!decision.approved || !decision.tool) {
4509
- return { decision, request: request3 };
5274
+ return { decision, request: request6 };
4510
5275
  }
4511
- const nextRequest = attachRuntimeToolsToDispatchRequest(request3, [decision.tool], options.attach);
5276
+ const nextRequest = attachRuntimeToolsToDispatchRequest(request6, [decision.tool], options.attach);
4512
5277
  return {
4513
5278
  decision,
4514
5279
  request: nextRequest
@@ -6758,15 +7523,15 @@ var DispatchEndpoint = class {
6758
7523
  * Attach approved runtime tools to a prompt step in a redispatch request.
6759
7524
  * Returns a new request object and does not mutate the original.
6760
7525
  */
6761
- attachApprovedRuntimeTools(request3, runtimeTools, options) {
6762
- return attachRuntimeToolsToDispatchRequest(request3, runtimeTools, options);
7526
+ attachApprovedRuntimeTools(request6, runtimeTools, options) {
7527
+ return attachRuntimeToolsToDispatchRequest(request6, runtimeTools, options);
6763
7528
  }
6764
7529
  /**
6765
7530
  * Validate a generated runtime tool proposal and attach it to the redispatch
6766
7531
  * request if approved, in one call.
6767
7532
  */
6768
- applyGeneratedRuntimeToolProposal(request3, proposal, options) {
6769
- return applyGeneratedRuntimeToolProposalToDispatchRequest(request3, proposal, options);
7533
+ applyGeneratedRuntimeToolProposal(request6, proposal, options) {
7534
+ return applyGeneratedRuntimeToolProposalToDispatchRequest(request6, proposal, options);
6770
7535
  }
6771
7536
  };
6772
7537
  var ChatEndpoint = class {
@@ -7318,8 +8083,8 @@ var GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
7318
8083
  },
7319
8084
  required: ["name", "description", "toolType", "parametersSchema", "config"]
7320
8085
  };
7321
- function appendRuntimeToolsToAgentRequest(request3, runtimeTools) {
7322
- const existing = request3.tools?.runtimeTools || [];
8086
+ function appendRuntimeToolsToAgentRequest(request6, runtimeTools) {
8087
+ const existing = request6.tools?.runtimeTools || [];
7323
8088
  const existingNames = new Set(existing.map((tool) => tool.name));
7324
8089
  const converted = runtimeTools.filter((tool) => !existingNames.has(tool.name)).map((tool) => ({
7325
8090
  name: tool.name,
@@ -7329,9 +8094,9 @@ function appendRuntimeToolsToAgentRequest(request3, runtimeTools) {
7329
8094
  ...tool.config ? { config: tool.config } : {}
7330
8095
  }));
7331
8096
  return {
7332
- ...request3,
8097
+ ...request6,
7333
8098
  tools: {
7334
- ...request3.tools,
8099
+ ...request6.tools,
7335
8100
  runtimeTools: [...existing, ...converted]
7336
8101
  }
7337
8102
  };
@@ -7407,21 +8172,21 @@ var _AgentsEndpoint = class _AgentsEndpoint {
7407
8172
  * Attach approved runtime tools to an agent execute request.
7408
8173
  * Returns a new request object and does not mutate the original.
7409
8174
  */
7410
- attachApprovedRuntimeTools(request3, runtimeTools) {
7411
- return appendRuntimeToolsToAgentRequest(request3, runtimeTools);
8175
+ attachApprovedRuntimeTools(request6, runtimeTools) {
8176
+ return appendRuntimeToolsToAgentRequest(request6, runtimeTools);
7412
8177
  }
7413
8178
  /**
7414
8179
  * Validate a generated runtime tool proposal and append it to an agent execute
7415
8180
  * request if approved, in one call.
7416
8181
  */
7417
- applyGeneratedRuntimeToolProposal(request3, proposal, options) {
8182
+ applyGeneratedRuntimeToolProposal(request6, proposal, options) {
7418
8183
  const decision = evaluateGeneratedRuntimeToolProposal(proposal, options);
7419
8184
  if (!decision.approved || !decision.tool) {
7420
- return { decision, request: request3 };
8185
+ return { decision, request: request6 };
7421
8186
  }
7422
8187
  return {
7423
8188
  decision,
7424
- request: appendRuntimeToolsToAgentRequest(request3, [decision.tool])
8189
+ request: appendRuntimeToolsToAgentRequest(request6, [decision.tool])
7425
8190
  };
7426
8191
  }
7427
8192
  /**
@@ -10522,6 +11287,29 @@ var BillingEndpoint = class {
10522
11287
  return this.client.get("/billing/spend-analytics", params);
10523
11288
  }
10524
11289
  };
11290
+ var ToolApprovalGrantsEndpoint = class {
11291
+ constructor(client) {
11292
+ this.client = client;
11293
+ }
11294
+ /**
11295
+ * List active remembered tool-approval grants for the authenticated owner,
11296
+ * optionally filtered to a single agent.
11297
+ */
11298
+ async list(agentId) {
11299
+ const query = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
11300
+ const response = await this.client.get(
11301
+ `/tool-approval-grants${query}`
11302
+ );
11303
+ return response.data;
11304
+ }
11305
+ /**
11306
+ * Revoke (soft-delete) a remembered grant so the tool prompts for approval
11307
+ * again on future dispatches.
11308
+ */
11309
+ async revoke(id) {
11310
+ return this.client.delete(`/tool-approval-grants/${id}`);
11311
+ }
11312
+ };
10525
11313
  var AppsEndpoint = class {
10526
11314
  constructor(client) {
10527
11315
  this.client = client;
@@ -10619,6 +11407,7 @@ var RuntypeClient2 = class {
10619
11407
  this.flowVersions = new FlowVersionsEndpoint(this);
10620
11408
  this.integrations = new IntegrationsEndpoint(this);
10621
11409
  this.billing = new BillingEndpoint(this);
11410
+ this.toolApprovalGrants = new ToolApprovalGrantsEndpoint(this);
10622
11411
  }
10623
11412
  /**
10624
11413
  * Set the API key for authentication
@@ -10657,7 +11446,7 @@ var RuntypeClient2 = class {
10657
11446
  clearApiKey() {
10658
11447
  delete this.headers.Authorization;
10659
11448
  }
10660
- async runWithLocalTools(request3, localTools, arg3, arg4) {
11449
+ async runWithLocalTools(request6, localTools, arg3, arg4) {
10661
11450
  const isOptionsObject = (val) => typeof val === "object" && val !== null && "scope" in val;
10662
11451
  const callbacks = isOptionsObject(arg3) ? void 0 : arg3;
10663
11452
  const options = (isOptionsObject(arg3) ? arg3 : arg4) ?? {};
@@ -10671,12 +11460,12 @@ var RuntypeClient2 = class {
10671
11460
  ...entry.pageOrigin ? { pageOrigin: entry.pageOrigin } : {}
10672
11461
  })) : [];
10673
11462
  const modifiedRequest = {
10674
- ...request3,
11463
+ ...request6,
10675
11464
  ...derivedClientTools.length > 0 ? {
10676
- clientTools: [...request3.clientTools ?? [], ...derivedClientTools]
11465
+ clientTools: [...request6.clientTools ?? [], ...derivedClientTools]
10677
11466
  } : {},
10678
11467
  options: {
10679
- ...request3.options || {},
11468
+ ...request6.options || {},
10680
11469
  streamResponse: isStreaming
10681
11470
  }
10682
11471
  };
@@ -11114,20 +11903,20 @@ var BatchBuilder = class {
11114
11903
  if (!this.recordType) {
11115
11904
  throw new Error("BatchBuilder: recordType is required. Call .forRecordType(type) first.");
11116
11905
  }
11117
- const request3 = {
11906
+ const request6 = {
11118
11907
  flowId: this.flowId,
11119
11908
  recordType: this.recordType
11120
11909
  };
11121
11910
  if (Object.keys(this.batchOptions).length > 0) {
11122
- request3.options = this.batchOptions;
11911
+ request6.options = this.batchOptions;
11123
11912
  }
11124
11913
  if (this.filterConfig) {
11125
- request3.filter = this.filterConfig;
11914
+ request6.filter = this.filterConfig;
11126
11915
  }
11127
11916
  if (this.limitConfig !== void 0) {
11128
- request3.limit = this.limitConfig;
11917
+ request6.limit = this.limitConfig;
11129
11918
  }
11130
- return request3;
11919
+ return request6;
11131
11920
  }
11132
11921
  /**
11133
11922
  * Execute the batch operation
@@ -11284,32 +12073,32 @@ var EvalBuilder = class {
11284
12073
  "EvalBuilder: records are required. Call .forRecordType(type) or .withRecords([...]) first."
11285
12074
  );
11286
12075
  }
11287
- const request3 = {};
12076
+ const request6 = {};
11288
12077
  if (this.flowId) {
11289
- request3.flowId = this.flowId;
12078
+ request6.flowId = this.flowId;
11290
12079
  } else if (this.virtualFlow) {
11291
- request3.flow = this.virtualFlow;
12080
+ request6.flow = this.virtualFlow;
11292
12081
  }
11293
12082
  if (this.recordType) {
11294
- request3.recordType = this.recordType;
12083
+ request6.recordType = this.recordType;
11295
12084
  } else if (this.inlineRecords) {
11296
- request3.records = this.inlineRecords;
12085
+ request6.records = this.inlineRecords;
11297
12086
  }
11298
12087
  if (this.modelOverrides) {
11299
- request3.modelOverrides = this.modelOverrides;
12088
+ request6.modelOverrides = this.modelOverrides;
11300
12089
  } else if (this.modelConfigs) {
11301
- request3.modelConfigs = this.modelConfigs;
12090
+ request6.modelConfigs = this.modelConfigs;
11302
12091
  }
11303
12092
  if (Object.keys(this.evalOptions).length > 0) {
11304
- request3.options = this.evalOptions;
12093
+ request6.options = this.evalOptions;
11305
12094
  }
11306
12095
  if (this.filterConfig) {
11307
- request3.filter = this.filterConfig;
12096
+ request6.filter = this.filterConfig;
11308
12097
  }
11309
12098
  if (this.limitConfig !== void 0) {
11310
- request3.limit = this.limitConfig;
12099
+ request6.limit = this.limitConfig;
11311
12100
  }
11312
- return request3;
12101
+ return request6;
11313
12102
  }
11314
12103
  /**
11315
12104
  * Execute the evaluation
@@ -11812,6 +12601,9 @@ export {
11812
12601
  LEDGER_ARTIFACT_LINE_PREFIX,
11813
12602
  LogsEndpoint,
11814
12603
  ModelConfigsEndpoint,
12604
+ ProductDriftError,
12605
+ ProductEnsureConflictError,
12606
+ ProductsNamespace,
11815
12607
  PromptRunner,
11816
12608
  PromptsEndpoint,
11817
12609
  PromptsNamespace,
@@ -11825,9 +12617,15 @@ export {
11825
12617
  STEP_TYPE_TO_METHOD,
11826
12618
  SchedulesEndpoint,
11827
12619
  SecretsEndpoint,
12620
+ SkillDriftError,
12621
+ SkillEnsureConflictError,
11828
12622
  SkillProposalsNamespace,
11829
12623
  SkillsNamespace,
12624
+ SurfaceDriftError,
12625
+ SurfaceEnsureConflictError,
11830
12626
  SurfacesEndpoint,
12627
+ SurfacesNamespace,
12628
+ ToolApprovalGrantsEndpoint,
11831
12629
  ToolDriftError,
11832
12630
  ToolEnsureConflictError,
11833
12631
  ToolsEndpoint,
@@ -11843,6 +12641,9 @@ export {
11843
12641
  compileWorkflowConfig,
11844
12642
  computeAgentContentHash,
11845
12643
  computeFlowContentHash,
12644
+ computeProductContentHash,
12645
+ computeSkillContentHash,
12646
+ computeSurfaceContentHash,
11846
12647
  computeToolContentHash,
11847
12648
  createClient,
11848
12649
  createExternalTool,
@@ -11851,6 +12652,9 @@ export {
11851
12652
  defineAgent,
11852
12653
  defineFlow,
11853
12654
  definePlaybook,
12655
+ defineProduct,
12656
+ defineSkill,
12657
+ defineSurface,
11854
12658
  defineTool,
11855
12659
  deployWorkflow,
11856
12660
  ensureDefaultWorkflowHooks,
@@ -11867,6 +12671,9 @@ export {
11867
12671
  listWorkflowHooks,
11868
12672
  normalizeAgentDefinition,
11869
12673
  normalizeCandidatePath,
12674
+ normalizeProductDefinition,
12675
+ normalizeSkillDefinition,
12676
+ normalizeSurfaceDefinition,
11870
12677
  normalizeToolDefinition,
11871
12678
  parseFinalBuffer,
11872
12679
  parseLedgerArtifactRelativePath,