Skip to content

Commit 0edb79f

Browse files
authored
feat(api): add optional log snapshots and Stripe subscription pagination (#8431)
* feat(logs): allow omitting workflow snapshots from diagnostic reads * feat(stripe): add optional subscription pagination cursors * fix: clarify Stripe guidance and log snapshot defaults
1 parent c82d618 commit 0edb79f

21 files changed

Lines changed: 410 additions & 38 deletions

File tree

‎apps/docs/content/docs/cli/logs.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ sim logs get <runId> [options]
3131

3232
| Option | Required | Description |
3333
| --- | --- | --- |
34+
| `--include-workflow-state` | No | Include the saved workflow snapshot (default: true). Set false to omit block configuration from a log read. Other run fields are unchanged. |
35+
| `--no-include-workflow-state` | No | Send --include-workflow-state as false. |
3436
| `--trace` | No | Show expanded trace spans with inputs, outputs, errors, timing, and cost. |
3537

3638
</CommandTable>

‎apps/docs/content/docs/cli/reference.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,8 @@ sim logs get <runId> [options]
27512751

27522752
| Option | Required | Description |
27532753
| --- | --- | --- |
2754+
| `--include-workflow-state` | No | Include the saved workflow snapshot (default: true). Set false to omit block configuration from a log read. Other run fields are unchanged. |
2755+
| `--no-include-workflow-state` | No | Send --include-workflow-state as false. |
27542756
| `--trace` | No | Show expanded trace spans with inputs, outputs, errors, timing, and cost. |
27552757

27562758
</CommandTable>

‎apps/docs/content/docs/integrations/stripe.mdx‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,7 @@ Cancel a subscription
12221222

12231223
### Stripe Resume Subscription
12241224

1225-
Resume a subscription that was scheduled for cancellation
1225+
Resume a paused subscription
12261226

12271227
#### Input
12281228

@@ -1299,7 +1299,7 @@ Resume a subscription that was scheduled for cancellation
12991299

13001300
### Stripe List Subscriptions
13011301

1302-
List all subscriptions
1302+
List one page of subscriptions. Follow starting_after while metadata.has_more is true.
13031303

13041304
#### Input
13051305

@@ -1309,6 +1309,8 @@ List all subscriptions
13091309
| `limit` | number | No | Number of results to return \(default 10, max 100\) |
13101310
| `customer` | string | No | Filter by customer ID |
13111311
| `status` | string | No | Filter by status \(active, past_due, unpaid, canceled, incomplete, incomplete_expired, trialing, all\) |
1312+
| `starting_after` | string | No | Next-page cursor: last subscription ID from the previous page. Do not combine with ending_before. |
1313+
| `ending_before` | string | No | Previous-page cursor: first subscription ID from the current page. Do not combine with starting_after. |
13121314
| `price` | string | No | Filter by price ID |
13131315

13141316
#### Output
@@ -1378,14 +1380,15 @@ List all subscriptions
13781380

13791381
### Stripe Search Subscriptions
13801382

1381-
Search for subscriptions using query syntax
1383+
Search one page of subscriptions using query syntax. Pass metadata.next_page as page to continue.
13821384

13831385
#### Input
13841386

13851387
| Parameter | Type | Required | Description |
13861388
| --------- | ---- | -------- | ----------- |
13871389
| `apiKey` | string | Yes | Stripe API key \(secret key\) |
1388-
| `query` | string | Yes | Search query \(e.g., "status:'active' AND customer:'cus_xxx'"\) |
1390+
| `query` | string | Yes | Search query \(e.g., "status:'active' AND metadata\['order_id'\]:'6735'"\) |
1391+
| `page` | string | No | Pagination token from metadata.next_page. Omit for the first page and keep the same query. |
13891392
| `limit` | number | No | Number of results to return \(default 10, max 100\) |
13901393

13911394
#### Output
@@ -1452,6 +1455,7 @@ Search for subscriptions using query syntax
14521455
| `metadata` | json | Search metadata |
14531456
| ↳ `count` | number | Number of items returned |
14541457
| ↳ `has_more` | boolean | Whether more items exist beyond this page |
1458+
| ↳ `next_page` | string | Token for the next page of search results, or null when there are no more results |
14551459

14561460
### Stripe Create Invoice
14571461

‎apps/docs/openapi-v2-logs.json‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,16 @@
383383
"pattern": "^[A-Za-z0-9._:-]+$",
384384
"description": "Unique workflow run identifier."
385385
}
386+
},
387+
{
388+
"name": "includeWorkflowState",
389+
"in": "query",
390+
"required": false,
391+
"description": "Include the saved workflow snapshot (default: true). Set false to omit block configuration from a log read. Other run fields are unchanged.",
392+
"schema": {
393+
"description": "Include the saved workflow snapshot (default: true). Set false to omit block configuration from a log read. Other run fields are unchanged.",
394+
"type": "boolean"
395+
}
386396
}
387397
],
388398
"responses": {
@@ -1602,7 +1612,7 @@
16021612
"type": "null"
16031613
}
16041614
],
1605-
"description": "Workflow graph captured for the run, or null if unavailable. Sensitive values are redacted to null; environment-variable references may be preserved."
1615+
"description": "Workflow graph captured for the run, or null if unavailable or includeWorkflowState=false. Sensitive values are redacted to null; environment-variable references may be preserved."
16061616
},
16071617
"traceSpans": {
16081618
"type": "array",

‎apps/sim/app/api/v2/logs/[runId]/route.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe('GET /api/v2/logs/[runId]', () => {
9999
expect(body.data).not.toHaveProperty('executionData')
100100
expect(mocks.execute).toHaveBeenCalledWith({
101101
principal: auth.principal,
102-
input: { runId: 'run-1' },
102+
input: { runId: 'run-1', includeWorkflowState: true },
103103
request,
104104
})
105105
})

‎apps/sim/app/api/v2/logs/[runId]/route.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export const GET = defineV2JsonRoute({
3636
operation: logOperations.readDetail,
3737
rateLimit: v2RateLimits.publicApi,
3838
errorPolicy: v2LogErrorPolicies.concealDetailAuthorization,
39-
mapInput: ({ params }) => ({ runId: params.runId }),
39+
mapInput: ({ params, query }) => ({
40+
runId: params.runId,
41+
includeWorkflowState: query.includeWorkflowState,
42+
}),
4043
useCase: getPublicLog,
4144
present: ({ log, workflowFolderPath, executionData, costLedger }) => {
4245
const detail: V2LogDetail = {

‎apps/sim/blocks/blocks/stripe.ts‎

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,30 @@ export const StripeBlock: BlockConfig<StripeResponse> = {
708708
},
709709
required: true,
710710
},
711+
{
712+
id: 'starting_after',
713+
title: 'Starting After',
714+
type: 'short-input',
715+
placeholder: 'Last subscription ID from the previous page',
716+
condition: { field: 'operation', value: 'list_subscriptions' },
717+
mode: 'advanced',
718+
},
719+
{
720+
id: 'ending_before',
721+
title: 'Ending Before',
722+
type: 'short-input',
723+
placeholder: 'First subscription ID from the current page',
724+
condition: { field: 'operation', value: 'list_subscriptions' },
725+
mode: 'advanced',
726+
},
727+
{
728+
id: 'page',
729+
title: 'Search Page',
730+
type: 'short-input',
731+
placeholder: 'Token from metadata.next_page; omit for the first page',
732+
condition: { field: 'operation', value: 'search_subscriptions' },
733+
mode: 'advanced',
734+
},
711735
// Additional filters for specific list operations
712736
{
713737
id: 'status',
@@ -976,6 +1000,9 @@ export const StripeBlock: BlockConfig<StripeResponse> = {
9761000
recurring: { type: 'json', description: 'Recurring billing configuration' },
9771001
// List/Search inputs
9781002
limit: { type: 'number', description: 'Maximum results to return' },
1003+
starting_after: { type: 'string', description: 'Next-page subscription ID cursor' },
1004+
ending_before: { type: 'string', description: 'Previous-page subscription ID cursor' },
1005+
page: { type: 'string', description: 'Subscription search token from metadata.next_page' },
9791006
query: { type: 'string', description: 'Search query' },
9801007
status: { type: 'string', description: 'Status filter' },
9811008
type: { type: 'string', description: 'Event type filter' },
@@ -1096,19 +1123,20 @@ export const StripeBlockMeta = {
10961123
description:
10971124
'Create and confirm a Stripe payment intent to collect a charge from a customer.',
10981125
content:
1099-
'# Collect Payment\n\nCharge a customer by creating and confirming a payment intent.\n\n## Steps\n1. Run Create Payment Intent with the amount, currency, and customer.\n2. Confirm the intent with Confirm Payment Intent, or Capture Payment Intent if it was created for manual capture.\n3. If a charge needs to be aborted, run Cancel Payment Intent instead.\n\n## Output\nReturn the payment intent ID, its status (succeeded, requires action, or canceled), and the captured amount.',
1126+
'# Collect Payment\n\nCharge a customer by creating and confirming a payment intent.\n\n## Steps\n1. Run Create Payment Intent with the amount, currency, and customer.\n2. Confirm the intent with Confirm Payment Intent and a payment method. If the confirmed intent has status requires_capture, run Capture Payment Intent to collect the authorized funds.\n3. If a charge needs to be aborted, run Cancel Payment Intent instead.\n\n## Output\nReturn the payment intent ID, its status (succeeded, requires action, or canceled), and the captured amount.',
11001127
},
11011128
{
11021129
name: 'manage-subscription',
1103-
description: 'Create, update, pause, or cancel a Stripe subscription for a customer.',
1130+
description:
1131+
'Create a Stripe subscription, manage its cancellation, or resume a paused subscription.',
11041132
content:
1105-
'# Manage Subscription\n\nHandle the lifecycle of a recurring subscription.\n\n## Steps\n1. To start a subscription, run Create Subscription with the customer and price items.\n2. To change a plan, run Update Subscription with the new items. To pause and later restart, use Cancel Subscription or Resume Subscription as appropriate.\n3. Confirm the current state with Retrieve Subscription.\n\n## Output\nReturn the subscription ID, its status, current period end, and the plan items, and note exactly what changed.',
1133+
'# Manage Subscription\n\nHandle the lifecycle of a recurring subscription.\n\n## Steps\n1. To start a subscription, run Create Subscription with the customer and price items.\n2. Use Update Subscription with cancel_at_period_end to schedule or reverse a cancellation before it takes effect. Use Cancel Subscription only for immediate cancellation. Resume Subscription applies only to a paused subscription; it does not restart a canceled subscription.\n3. Confirm the current state with Retrieve Subscription.\n\n## Output\nReturn the subscription ID, its status, and the returned subscription items, and note exactly what changed.',
11061134
},
11071135
{
11081136
name: 'issue-invoice',
11091137
description: 'Create, finalize, and send a Stripe invoice to a customer, then track payment.',
11101138
content:
1111-
'# Issue Invoice\n\nBill a customer with a Stripe invoice.\n\n## Steps\n1. Run Create Invoice for the customer with the line items.\n2. Run Finalize Invoice to lock it, then Send Invoice to deliver it to the customer.\n3. Track payment with Retrieve Invoice, or run Pay Invoice to charge a saved payment method. Use Void Invoice to cancel an unpaid invoice.\n\n## Output\nReturn the invoice ID, its status (draft, open, paid, or void), the amount due, and the hosted invoice URL when available.',
1139+
'# Issue Invoice\n\nBill a customer with a Stripe invoice.\n\n## Steps\n1. Run Create Invoice for the customer. This tool does not add line items; add them in Stripe and verify the draft with Retrieve Invoice before finalizing.\n2. Run Finalize Invoice to lock it, then Send Invoice to deliver it to the customer.\n3. Track payment with Retrieve Invoice, or run Pay Invoice to charge a saved payment method. Use Void Invoice to cancel an unpaid invoice.\n\n## Output\nReturn the invoice ID, its status (draft, open, paid, or void), the amount due, and the hosted invoice URL when available.',
11121140
},
11131141
{
11141142
name: 'find-customer-activity',

‎apps/sim/lib/api/contracts/v2/logs.ts‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { z } from 'zod'
22
import { traceSpansSchema } from '@/lib/api/contracts/logs'
33
import {
44
booleanQueryFlagSchema,
5-
noInputSchema,
65
runIdSchema,
76
workspaceIdSchema,
87
} from '@/lib/api/contracts/primitives'
@@ -24,9 +23,8 @@ import { v2RunFileSchema } from '@/lib/api/contracts/v2/workflows'
2423
import { PERSISTED_WORKFLOW_EXECUTION_STATUSES } from '@/lib/logs/types'
2524

2625
/**
27-
* v2 logs contracts. The query schemas are reused verbatim from v1 (the request
28-
* shape is unchanged); only the response envelope is upgraded to the canonical
29-
* v2 shapes with concrete item schemas.
26+
* v2 logs contracts. List queries retain the v1 filters; responses use the
27+
* canonical v2 envelope and concrete item schemas.
3028
*/
3129

3230
const v2LogCostSchema = z
@@ -164,7 +162,7 @@ const v2LogWorkflowStateSchema = z
164162
)
165163
.nullable()
166164
.describe(
167-
'Workflow graph captured for the run, or null if unavailable. Sensitive values are redacted to null; environment-variable references may be preserved.'
165+
'Workflow graph captured for the run, or null if unavailable or includeWorkflowState=false. Sensitive values are redacted to null; environment-variable references may be preserved.'
168166
)
169167

170168
const v2LogWorkflowSummarySchema = z.object({
@@ -717,7 +715,21 @@ export const v2ListLogsContract = defineRouteContract({
717715
export const v2GetLogContract = defineRouteContract({
718716
method: 'GET',
719717
path: '/api/v2/logs/[runId]',
720-
query: noInputSchema,
718+
query: z
719+
.object({
720+
includeWorkflowState: booleanQueryFlagSchema
721+
.default(true)
722+
.describe(
723+
'Include the saved workflow snapshot (default: true). Set false to omit block configuration from a log read. Other run fields are unchanged.'
724+
),
725+
})
726+
.strict()
727+
.meta({
728+
id: 'GetLogQuery',
729+
title: 'Execution log detail options',
730+
description: 'Controls whether a log detail read includes its saved workflow snapshot.',
731+
examples: [{ includeWorkflowState: false }],
732+
}),
721733
params: v2LogParamsSchema,
722734
response: {
723735
mode: 'json',

‎apps/sim/lib/logs/application/get-public-log.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface PublicLogContext extends ActiveWorkspaceApplicationContext {
3030

3131
export interface GetPublicLogInput {
3232
runId: string
33+
includeWorkflowState?: boolean
3334
}
3435

3536
export interface GetPublicLogResult {
@@ -94,7 +95,7 @@ export const getPublicLog = defineAuthorizedWorkspaceUseCase({
9495
return { ...workspace, executionId: scope.executionId, workflowId: scope.workflowId }
9596
},
9697
authorizationOptions: logDelegationAuthorization<PublicLogContext>(),
97-
execute: async ({ principal, context }): Promise<GetPublicLogResult> => {
98+
execute: async ({ principal, input, context }): Promise<GetPublicLogResult> => {
9899
/**
99100
* Attribution and the projection subject in one value; a workspace API key
100101
* represents no user and therefore reads the run whole. See
@@ -118,7 +119,8 @@ export const getPublicLog = defineAuthorizedWorkspaceUseCase({
118119

119120
const log = await getPublicWorkflowLog(
120121
{ column: 'executionId', value: context.executionId },
121-
context.workspaceId
122+
context.workspaceId,
123+
{ includeWorkflowState: input.includeWorkflowState }
122124
)
123125
if (!log || log.workflowId !== context.workflowId) {
124126
throw new OrchestrationError('not_found', 'Log not found')

‎apps/sim/lib/logs/application/public-log-use-cases.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ describe('public log application use cases', () => {
139139
expect(mocks.loadWorkspace).toHaveBeenCalledWith('workspace-1')
140140
expect(mocks.getLog).toHaveBeenCalledWith(
141141
{ column: 'executionId', value: 'run-1' },
142-
'workspace-1'
142+
'workspace-1',
143+
{ includeWorkflowState: undefined }
143144
)
144145
expect(mocks.materialize).toHaveBeenCalledWith(
145146
{ pointer: true },

0 commit comments

Comments
 (0)