@siglume/api-sdk 1.0.0 → 1.1.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/README.md CHANGED
@@ -83,6 +83,57 @@ includes runtime checks, contract checks, external OAuth declaration checks, pri
83
83
  rules, and a mandatory fail-closed LLM legal review for law compliance plus
84
84
  public-order / morals compliance.
85
85
 
86
+ ## Usage-Based And Per-Action Billing
87
+
88
+ Use `price_model: PriceModel.USAGE_BASED` or `PriceModel.PER_ACTION` when the
89
+ API must execute before the final operation is known. These listings are free to
90
+ invoke up front. Your adapter returns the executed operation in
91
+ `ExecutionResult.receipt_summary`; the matching `pricing_plan` item sets the
92
+ charge:
93
+
94
+ ```ts
95
+ return {
96
+ success: true,
97
+ output: { posted: true, post_url: "https://x.com/..." },
98
+ units_consumed: 1,
99
+ amount_minor: 20,
100
+ currency: "JPY",
101
+ receipt_summary: {
102
+ operation: "url_post",
103
+ amount_minor: 20,
104
+ currency: "JPY",
105
+ },
106
+ };
107
+ ```
108
+
109
+ Set `price_value_minor: 0` when prices vary by operation, and publish a
110
+ buyer-facing `pricing_plan` so API Store and Game API Store can show the plan.
111
+ `pricing_plan.items` is required for `usage_based` and `per_action` listings:
112
+
113
+ ```ts
114
+ pricing_plan: {
115
+ display_name: "Operation prices",
116
+ currency: "JPY",
117
+ free_upfront_invocation: true,
118
+ items: [
119
+ { key: "connection_check", label: "Connection check", price_minor: 0 },
120
+ { key: "dry_run", label: "Dry-run preview", price_minor: 0 },
121
+ { key: "text_post", label: "Text post", price_minor: 15 },
122
+ { key: "url_post", label: "URL post", price_minor: 20 },
123
+ { key: "reply", label: "Reply", price_minor: 30 },
124
+ ],
125
+ }
126
+ ```
127
+
128
+ The `pricing_plan` is authoritative. If the adapter returns a conflicting
129
+ positive amount, the platform rejects the call instead of charging an arbitrary
130
+ API-declared amount. `0` is valid for free operations. For JPY/JPYC billing,
131
+ positive operation prices must be at least `15` minor units; `1` through `14`
132
+ are rejected by the SDK and platform because platform-sponsored gas can exceed
133
+ the fee.
134
+ `units_consumed` is kept for receipts and analytics; it does not multiply a
135
+ request-type plan price.
136
+
86
137
  Company-name publishing is founder-only in the Phase 2 MVP. Use
87
138
  `publisher_type: "company"` with `company_id` in `app_manifest.yaml`, or pass
88
139
  `--company <company_id>` to the CLI. Paid company listings require the
@@ -176,6 +176,61 @@ var init_utils = __esm({
176
176
  }
177
177
  });
178
178
 
179
+ // src/types.ts
180
+ var PermissionClass, ApprovalMode, Environment, PriceModel, AppCategory, MINIMUM_JPY_OPERATION_PRICE_MINOR, ToolManualPermissionClass, SettlementMode;
181
+ var init_types = __esm({
182
+ "src/types.ts"() {
183
+ "use strict";
184
+ PermissionClass = {
185
+ READ_ONLY: "read-only",
186
+ ACTION: "action",
187
+ PAYMENT: "payment",
188
+ /** @deprecated Use READ_ONLY. Behaves identically. */
189
+ RECOMMENDATION: "recommendation"
190
+ };
191
+ ApprovalMode = {
192
+ AUTO: "auto",
193
+ BUDGET_BOUNDED: "budget-bounded",
194
+ ALWAYS_ASK: "always-ask",
195
+ DENY: "deny"
196
+ };
197
+ Environment = {
198
+ SANDBOX: "sandbox",
199
+ LIVE: "live"
200
+ };
201
+ PriceModel = {
202
+ FREE: "free",
203
+ SUBSCRIPTION: "subscription",
204
+ ONE_TIME: "one_time",
205
+ BUNDLE: "bundle",
206
+ USAGE_BASED: "usage_based",
207
+ PER_ACTION: "per_action"
208
+ };
209
+ AppCategory = {
210
+ COMMERCE: "commerce",
211
+ BOOKING: "booking",
212
+ CRM: "crm",
213
+ FINANCE: "finance",
214
+ DOCUMENT: "document",
215
+ COMMUNICATION: "communication",
216
+ MONITORING: "monitoring",
217
+ OTHER: "other"
218
+ };
219
+ MINIMUM_JPY_OPERATION_PRICE_MINOR = 15;
220
+ ToolManualPermissionClass = {
221
+ READ_ONLY: "read_only",
222
+ ACTION: "action",
223
+ PAYMENT: "payment"
224
+ };
225
+ SettlementMode = {
226
+ STRIPE_CHECKOUT: "stripe_checkout",
227
+ STRIPE_PAYMENT_INTENT: "stripe_payment_intent",
228
+ POLYGON_MANDATE: "polygon_mandate",
229
+ EMBEDDED_WALLET_CHARGE: "embedded_wallet_charge"
230
+ };
231
+ }
232
+ });
233
+
179
234
  // src/webhooks.ts
180
235
  function isRecord2(value) {
181
236
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1297,6 +1352,58 @@ function validateSaveDataSchema(schema, fieldName) {
1297
1352
  }
1298
1353
  }
1299
1354
  }
1355
+ function validatePricingPlanFloor(plan, defaultCurrency) {
1356
+ if (plan === void 0 || plan === null) {
1357
+ return;
1358
+ }
1359
+ if (!isRecord(plan)) {
1360
+ throw new SiglumeClientError("AppManifest.pricing_plan must be an object when provided.");
1361
+ }
1362
+ const items = plan.items;
1363
+ if (items === void 0 || items === null) {
1364
+ return;
1365
+ }
1366
+ if (!Array.isArray(items)) {
1367
+ throw new SiglumeClientError("AppManifest.pricing_plan.items must be an array when provided.");
1368
+ }
1369
+ const planCurrency = String(plan.currency ?? defaultCurrency ?? "").trim().toUpperCase();
1370
+ const seenKeys = /* @__PURE__ */ new Set();
1371
+ items.forEach((item, index) => {
1372
+ if (!isRecord(item)) {
1373
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}] must be an object.`);
1374
+ }
1375
+ const itemKey = String(
1376
+ item.key ?? item.operation ?? item.operation_key ?? item.request_type ?? item.receipt_code ?? item.action ?? ""
1377
+ ).trim();
1378
+ if (!itemKey) {
1379
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}].key is required.`);
1380
+ }
1381
+ if (seenKeys.has(itemKey)) {
1382
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}].key duplicates ${itemKey}.`);
1383
+ }
1384
+ seenKeys.add(itemKey);
1385
+ const amountRaw = item.price_minor ?? item.amount_minor ?? item.cost_minor ?? item.value_minor;
1386
+ if (amountRaw === void 0 || amountRaw === null) {
1387
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}].price_minor is required.`);
1388
+ }
1389
+ const amountMinor = typeof amountRaw === "number" ? amountRaw : typeof amountRaw === "string" && amountRaw.trim() ? Number(amountRaw) : NaN;
1390
+ if (!Number.isInteger(amountMinor)) {
1391
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}].price_minor must be an integer.`);
1392
+ }
1393
+ if (amountMinor < 0) {
1394
+ throw new SiglumeClientError(`AppManifest.pricing_plan.items[${index}].price_minor must be zero or positive.`);
1395
+ }
1396
+ const currency = String(item.currency ?? planCurrency ?? defaultCurrency ?? "").trim().toUpperCase();
1397
+ if (MINIMUM_JPY_OPERATION_PRICE_CURRENCIES.has(currency) && amountMinor > 0 && amountMinor < MINIMUM_JPY_OPERATION_PRICE_MINOR) {
1398
+ throw new SiglumeClientError(
1399
+ `AppManifest.pricing_plan.items[${index}].price_minor must be 0 or at least ${MINIMUM_JPY_OPERATION_PRICE_MINOR} for JPY/JPYC operation billing.`
1400
+ );
1401
+ }
1402
+ });
1403
+ }
1404
+ function pricingPlanHasItems(plan) {
1405
+ return isRecord(plan) && Array.isArray(plan.items) && plan.items.length > 0;
1406
+ }
1300
1407
  function buildToolManualQualityReport(payload) {
1301
1408
  const qualityBlock = isRecord(payload.quality) ? payload.quality : payload;
1302
1409
  const issues = [];
@@ -1372,6 +1479,7 @@ function buildUrl(baseUrl, path, params) {
1372
1479
  }
1373
1480
  function parseListing(data) {
1374
1481
  const metadata = isRecord(data.metadata) ? data.metadata : {};
1482
+ const pricing_plan = isRecord(data.pricing_plan) ? data.pricing_plan : isRecord(metadata.pricing_plan) ? metadata.pricing_plan : null;
1375
1483
  const persistence = isRecord(data.persistence) ? data.persistence : isRecord(metadata.persistence) ? metadata.persistence : {};
1376
1484
  return {
1377
1485
  listing_id: String(data.listing_id ?? data.id ?? ""),
@@ -1385,6 +1493,7 @@ function parseListing(data) {
1385
1493
  dry_run_supported: Boolean(data.dry_run_supported ?? false),
1386
1494
  price_model: stringOrNull(data.price_model),
1387
1495
  price_value_minor: Number(data.price_value_minor ?? 0),
1496
+ pricing_plan,
1388
1497
  currency: String(data.currency ?? "USD"),
1389
1498
  allow_free_trial: Boolean(data.allow_free_trial ?? false),
1390
1499
  free_trial_duration_days: Number(data.free_trial_duration_days ?? 30),
@@ -2482,10 +2591,11 @@ function cloneJsonLike(value) {
2482
2591
  }
2483
2592
  return value;
2484
2593
  }
2485
- var DEFAULT_SIGLUME_API_BASE, RETRYABLE_STATUS_CODES, CursorPageResult, SiglumeClient;
2594
+ var DEFAULT_SIGLUME_API_BASE, RETRYABLE_STATUS_CODES, MINIMUM_JPY_OPERATION_PRICE_CURRENCIES, CursorPageResult, SiglumeClient;
2486
2595
  var init_client = __esm({
2487
2596
  "src/client.ts"() {
2488
2597
  "use strict";
2598
+ init_types();
2489
2599
  init_errors();
2490
2600
  init_webhooks();
2491
2601
  init_web3();
@@ -2493,6 +2603,7 @@ var init_client = __esm({
2493
2603
  init_utils();
2494
2604
  DEFAULT_SIGLUME_API_BASE = "https://siglume.com/v1";
2495
2605
  RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
2606
+ MINIMUM_JPY_OPERATION_PRICE_CURRENCIES = /* @__PURE__ */ new Set(["JPY", "JPYC"]);
2496
2607
  CursorPageResult = class {
2497
2608
  items;
2498
2609
  next_cursor;
@@ -2600,6 +2711,7 @@ var init_client = __esm({
2600
2711
  "jurisdiction",
2601
2712
  "price_model",
2602
2713
  "price_value_minor",
2714
+ "pricing_plan",
2603
2715
  "currency",
2604
2716
  "allow_free_trial",
2605
2717
  "free_trial_duration_days",
@@ -2616,6 +2728,9 @@ var init_client = __esm({
2616
2728
  payload[fieldName] = value;
2617
2729
  }
2618
2730
  }
2731
+ if (payload.pricing_plan !== void 0 && (typeof payload.pricing_plan !== "object" || Array.isArray(payload.pricing_plan))) {
2732
+ throw new SiglumeClientError("AppManifest.pricing_plan must be an object when provided.");
2733
+ }
2619
2734
  if (payload.store_vertical === void 0 || payload.store_vertical === null) {
2620
2735
  throw new SiglumeClientError(
2621
2736
  "AppManifest.store_vertical is required. Choose 'api' for normal API Store listings or 'game' for API games."
@@ -2631,6 +2746,13 @@ var init_client = __esm({
2631
2746
  throw new SiglumeClientError(`AppManifest.currency must be 'USD' or 'JPY'. Got ${String(payload.currency)}.`);
2632
2747
  }
2633
2748
  payload.currency = currency;
2749
+ if (payload.pricing_plan !== void 0) {
2750
+ validatePricingPlanFloor(payload.pricing_plan, currency);
2751
+ }
2752
+ const priceModel = String(payload.price_model ?? "free").trim().toLowerCase();
2753
+ if ((priceModel === "usage_based" || priceModel === "per_action") && !pricingPlanHasItems(payload.pricing_plan)) {
2754
+ throw new SiglumeClientError("AppManifest.pricing_plan.items is required for usage_based/per_action pricing.");
2755
+ }
2634
2756
  if (payload.allow_free_trial === void 0 || payload.allow_free_trial === null) {
2635
2757
  throw new SiglumeClientError(
2636
2758
  "AppManifest.allow_free_trial is required. Pass true to offer a Plus/Pro buyer free trial or false to disable trials."
@@ -5305,53 +5427,8 @@ function stableValue(value) {
5305
5427
  return value;
5306
5428
  }
5307
5429
 
5308
- // src/types.ts
5309
- var PermissionClass = {
5310
- READ_ONLY: "read-only",
5311
- ACTION: "action",
5312
- PAYMENT: "payment",
5313
- /** @deprecated Use READ_ONLY. Behaves identically. */
5314
- RECOMMENDATION: "recommendation"
5315
- };
5316
- var ApprovalMode = {
5317
- AUTO: "auto",
5318
- BUDGET_BOUNDED: "budget-bounded",
5319
- ALWAYS_ASK: "always-ask",
5320
- DENY: "deny"
5321
- };
5322
- var Environment = {
5323
- SANDBOX: "sandbox",
5324
- LIVE: "live"
5325
- };
5326
- var PriceModel = {
5327
- FREE: "free",
5328
- SUBSCRIPTION: "subscription",
5329
- ONE_TIME: "one_time",
5330
- BUNDLE: "bundle",
5331
- USAGE_BASED: "usage_based",
5332
- PER_ACTION: "per_action"
5333
- };
5334
- var AppCategory = {
5335
- COMMERCE: "commerce",
5336
- BOOKING: "booking",
5337
- CRM: "crm",
5338
- FINANCE: "finance",
5339
- DOCUMENT: "document",
5340
- COMMUNICATION: "communication",
5341
- MONITORING: "monitoring",
5342
- OTHER: "other"
5343
- };
5344
- var ToolManualPermissionClass = {
5345
- READ_ONLY: "read_only",
5346
- ACTION: "action",
5347
- PAYMENT: "payment"
5348
- };
5349
- var SettlementMode = {
5350
- STRIPE_CHECKOUT: "stripe_checkout",
5351
- STRIPE_PAYMENT_INTENT: "stripe_payment_intent",
5352
- POLYGON_MANDATE: "polygon_mandate",
5353
- EMBEDDED_WALLET_CHARGE: "embedded_wallet_charge"
5354
- };
5430
+ // src/runtime.ts
5431
+ init_types();
5355
5432
 
5356
5433
  // src/testing/recorder.ts
5357
5434
  var CASSETTE_VERSION = 1;
@@ -5743,6 +5820,7 @@ Actual: ${requestSignature(requestRecord, ignoreBodyFields)}`
5743
5820
  };
5744
5821
 
5745
5822
  // src/tool-manual-validator.ts
5823
+ init_types();
5746
5824
  init_utils();
5747
5825
  var JURISDICTION_PATTERN = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
5748
5826
  var TOOL_NAME_RE = /^[A-Za-z0-9_]{3,64}$/;
@@ -6014,6 +6092,64 @@ function validate_tool_manual(manualInput) {
6014
6092
  // src/runtime.ts
6015
6093
  init_web3();
6016
6094
  var CAPABILITY_KEY_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
6095
+ var MINIMUM_JPY_OPERATION_PRICE_CURRENCIES2 = /* @__PURE__ */ new Set(["JPY", "JPYC"]);
6096
+ function pricingPlanFloorIssues(plan, defaultCurrency) {
6097
+ const issues = [];
6098
+ if (plan === void 0 || plan === null) {
6099
+ return issues;
6100
+ }
6101
+ if (typeof plan !== "object" || Array.isArray(plan)) {
6102
+ return ["pricing_plan must be an object when provided"];
6103
+ }
6104
+ const record = plan;
6105
+ const items = record.items;
6106
+ if (items === void 0 || items === null) {
6107
+ return issues;
6108
+ }
6109
+ if (!Array.isArray(items)) {
6110
+ return ["pricing_plan.items must be an array when provided"];
6111
+ }
6112
+ const planCurrency = String(record.currency ?? defaultCurrency ?? "").trim().toUpperCase();
6113
+ const seenKeys = /* @__PURE__ */ new Set();
6114
+ items.forEach((item, index) => {
6115
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
6116
+ issues.push(`pricing_plan.items[${index}] must be an object`);
6117
+ return;
6118
+ }
6119
+ const itemRecord = item;
6120
+ const itemKey = String(
6121
+ itemRecord.key ?? itemRecord.operation ?? itemRecord.operation_key ?? itemRecord.request_type ?? itemRecord.receipt_code ?? itemRecord.action ?? ""
6122
+ ).trim();
6123
+ if (!itemKey) {
6124
+ issues.push(`pricing_plan.items[${index}].key is required`);
6125
+ } else if (seenKeys.has(itemKey)) {
6126
+ issues.push(`pricing_plan.items[${index}].key duplicates ${itemKey}`);
6127
+ } else {
6128
+ seenKeys.add(itemKey);
6129
+ }
6130
+ const amountRaw = itemRecord.price_minor ?? itemRecord.amount_minor ?? itemRecord.cost_minor ?? itemRecord.value_minor;
6131
+ if (amountRaw === void 0 || amountRaw === null) {
6132
+ issues.push(`pricing_plan.items[${index}].price_minor is required`);
6133
+ return;
6134
+ }
6135
+ const amountMinor = typeof amountRaw === "number" ? amountRaw : typeof amountRaw === "string" && amountRaw.trim() ? Number(amountRaw) : NaN;
6136
+ if (!Number.isInteger(amountMinor)) {
6137
+ issues.push(`pricing_plan.items[${index}].price_minor must be an integer`);
6138
+ return;
6139
+ }
6140
+ if (amountMinor < 0) {
6141
+ issues.push(`pricing_plan.items[${index}].price_minor must be zero or positive`);
6142
+ return;
6143
+ }
6144
+ const currency = String(itemRecord.currency ?? planCurrency ?? defaultCurrency ?? "").trim().toUpperCase();
6145
+ if (MINIMUM_JPY_OPERATION_PRICE_CURRENCIES2.has(currency) && amountMinor > 0 && amountMinor < MINIMUM_JPY_OPERATION_PRICE_MINOR) {
6146
+ issues.push(
6147
+ `pricing_plan.items[${index}].price_minor must be 0 or at least ${MINIMUM_JPY_OPERATION_PRICE_MINOR} for JPY/JPYC operation billing`
6148
+ );
6149
+ }
6150
+ });
6151
+ return issues;
6152
+ }
6017
6153
  function normalizeExecutionResult(result, executionKind) {
6018
6154
  return {
6019
6155
  success: Boolean(result.success),
@@ -6101,6 +6237,10 @@ var AppTestHarness = class {
6101
6237
  if (!manifest.example_prompts || manifest.example_prompts.length === 0) {
6102
6238
  issues.push("at least one example_prompt is recommended");
6103
6239
  }
6240
+ issues.push(...pricingPlanFloorIssues(manifest.pricing_plan, String(manifest.currency ?? "USD")));
6241
+ if ((manifest.price_model === PriceModel.USAGE_BASED || manifest.price_model === PriceModel.PER_ACTION) && (!manifest.pricing_plan || !Array.isArray(manifest.pricing_plan.items) || manifest.pricing_plan.items.length === 0)) {
6242
+ issues.push("pricing_plan.items is required for usage_based/per_action pricing");
6243
+ }
6104
6244
  if (manifest.permission_class === PermissionClass.ACTION || manifest.permission_class === PermissionClass.PAYMENT) {
6105
6245
  if (!manifest.dry_run_supported) {
6106
6246
  issues.push("action/payment apps should support dry_run");
@@ -6176,7 +6316,7 @@ var AppTestHarness = class {
6176
6316
  };
6177
6317
  }
6178
6318
  return {
6179
- experimental: manifest.price_model === PriceModel.USAGE_BASED || manifest.price_model === PriceModel.PER_ACTION,
6319
+ experimental: false,
6180
6320
  usage_record,
6181
6321
  invoice_line_preview
6182
6322
  };