@viewportai/daemon 0.9.1 → 0.11.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.
Files changed (113) hide show
  1. package/dist/agents/custom-command.d.ts +3 -0
  2. package/dist/agents/custom-command.d.ts.map +1 -0
  3. package/dist/agents/custom-command.js +58 -0
  4. package/dist/agents/custom-command.js.map +1 -0
  5. package/dist/cli/workflow-commands.d.ts.map +1 -1
  6. package/dist/cli/workflow-commands.js +142 -4
  7. package/dist/cli/workflow-commands.js.map +1 -1
  8. package/dist/cli/workflow-managed-worker-format.d.ts +10 -0
  9. package/dist/cli/workflow-managed-worker-format.d.ts.map +1 -0
  10. package/dist/cli/workflow-managed-worker-format.js +120 -0
  11. package/dist/cli/workflow-managed-worker-format.js.map +1 -0
  12. package/dist/cli/workflow-managed-worker-types.d.ts +55 -0
  13. package/dist/cli/workflow-managed-worker-types.d.ts.map +1 -0
  14. package/dist/cli/workflow-managed-worker-types.js +2 -0
  15. package/dist/cli/workflow-managed-worker-types.js.map +1 -0
  16. package/dist/cli/workflow-managed-worker-util.d.ts +5 -0
  17. package/dist/cli/workflow-managed-worker-util.d.ts.map +1 -0
  18. package/dist/cli/workflow-managed-worker-util.js +28 -0
  19. package/dist/cli/workflow-managed-worker-util.js.map +1 -0
  20. package/dist/cli/workflow-managed-worker.d.ts +2 -0
  21. package/dist/cli/workflow-managed-worker.d.ts.map +1 -0
  22. package/dist/cli/workflow-managed-worker.js +279 -0
  23. package/dist/cli/workflow-managed-worker.js.map +1 -0
  24. package/dist/server/http-request-schemas.d.ts +6 -0
  25. package/dist/server/http-request-schemas.d.ts.map +1 -1
  26. package/dist/server/http-request-schemas.js +2 -0
  27. package/dist/server/http-request-schemas.js.map +1 -1
  28. package/dist/server/http-server.d.ts.map +1 -1
  29. package/dist/server/http-server.js +13 -0
  30. package/dist/server/http-server.js.map +1 -1
  31. package/dist/startup-agents.d.ts.map +1 -1
  32. package/dist/startup-agents.js +5 -0
  33. package/dist/startup-agents.js.map +1 -1
  34. package/dist/workflows/action-adapters.d.ts +7 -0
  35. package/dist/workflows/action-adapters.d.ts.map +1 -0
  36. package/dist/workflows/action-adapters.js +152 -0
  37. package/dist/workflows/action-adapters.js.map +1 -0
  38. package/dist/workflows/action-digest.d.ts +8 -0
  39. package/dist/workflows/action-digest.d.ts.map +1 -0
  40. package/dist/workflows/action-digest.js +36 -0
  41. package/dist/workflows/action-digest.js.map +1 -0
  42. package/dist/workflows/action-execution-ledger.d.ts +8 -0
  43. package/dist/workflows/action-execution-ledger.d.ts.map +1 -0
  44. package/dist/workflows/action-execution-ledger.js +57 -0
  45. package/dist/workflows/action-execution-ledger.js.map +1 -0
  46. package/dist/workflows/action-policy.d.ts +3 -0
  47. package/dist/workflows/action-policy.d.ts.map +1 -0
  48. package/dist/workflows/action-policy.js +13 -0
  49. package/dist/workflows/action-policy.js.map +1 -0
  50. package/dist/workflows/action-provider-adapters.d.ts +13 -0
  51. package/dist/workflows/action-provider-adapters.d.ts.map +1 -0
  52. package/dist/workflows/action-provider-adapters.js +284 -0
  53. package/dist/workflows/action-provider-adapters.js.map +1 -0
  54. package/dist/workflows/context-node-resolver.d.ts +3 -0
  55. package/dist/workflows/context-node-resolver.d.ts.map +1 -0
  56. package/dist/workflows/context-node-resolver.js +122 -0
  57. package/dist/workflows/context-node-resolver.js.map +1 -0
  58. package/dist/workflows/event-types.d.ts +1 -1
  59. package/dist/workflows/event-types.d.ts.map +1 -1
  60. package/dist/workflows/node-executor.d.ts.map +1 -1
  61. package/dist/workflows/node-executor.js +24 -0
  62. package/dist/workflows/node-executor.js.map +1 -1
  63. package/dist/workflows/node-registry.d.ts.map +1 -1
  64. package/dist/workflows/node-registry.js +115 -1
  65. package/dist/workflows/node-registry.js.map +1 -1
  66. package/dist/workflows/parser.js +30 -0
  67. package/dist/workflows/parser.js.map +1 -1
  68. package/dist/workflows/platform-command-applier.d.ts.map +1 -1
  69. package/dist/workflows/platform-command-applier.js +4 -0
  70. package/dist/workflows/platform-command-applier.js.map +1 -1
  71. package/dist/workflows/platform-runtime-command.d.ts +2 -0
  72. package/dist/workflows/platform-runtime-command.d.ts.map +1 -1
  73. package/dist/workflows/platform-runtime-command.js +9 -0
  74. package/dist/workflows/platform-runtime-command.js.map +1 -1
  75. package/dist/workflows/plugin-loader.d.ts.map +1 -1
  76. package/dist/workflows/plugin-loader.js +6 -0
  77. package/dist/workflows/plugin-loader.js.map +1 -1
  78. package/dist/workflows/preflight.d.ts.map +1 -1
  79. package/dist/workflows/preflight.js +6 -2
  80. package/dist/workflows/preflight.js.map +1 -1
  81. package/dist/workflows/run-types.d.ts +19 -0
  82. package/dist/workflows/run-types.d.ts.map +1 -1
  83. package/dist/workflows/runner-hook-events.d.ts +19 -0
  84. package/dist/workflows/runner-hook-events.d.ts.map +1 -0
  85. package/dist/workflows/runner-hook-events.js +16 -0
  86. package/dist/workflows/runner-hook-events.js.map +1 -0
  87. package/dist/workflows/runner-shared.d.ts.map +1 -1
  88. package/dist/workflows/runner-shared.js +26 -2
  89. package/dist/workflows/runner-shared.js.map +1 -1
  90. package/dist/workflows/runner.d.ts +0 -1
  91. package/dist/workflows/runner.d.ts.map +1 -1
  92. package/dist/workflows/runner.js +31 -17
  93. package/dist/workflows/runner.js.map +1 -1
  94. package/dist/workflows/types.d.ts +55 -13
  95. package/dist/workflows/types.d.ts.map +1 -1
  96. package/dist/workflows/workflow-production-schema.d.ts +100 -0
  97. package/dist/workflows/workflow-production-schema.d.ts.map +1 -0
  98. package/dist/workflows/workflow-production-schema.js +107 -0
  99. package/dist/workflows/workflow-production-schema.js.map +1 -0
  100. package/dist/workflows/workflow-production-types.d.ts +71 -0
  101. package/dist/workflows/workflow-production-types.d.ts.map +1 -0
  102. package/dist/workflows/workflow-production-types.js +2 -0
  103. package/dist/workflows/workflow-production-types.js.map +1 -0
  104. package/dist/workflows/workflow-schema-common.d.ts +86 -0
  105. package/dist/workflows/workflow-schema-common.d.ts.map +1 -0
  106. package/dist/workflows/workflow-schema-common.js +79 -0
  107. package/dist/workflows/workflow-schema-common.js.map +1 -0
  108. package/dist/workflows/workflow-schema.d.ts +454 -3
  109. package/dist/workflows/workflow-schema.d.ts.map +1 -1
  110. package/dist/workflows/workflow-schema.js +63 -77
  111. package/dist/workflows/workflow-schema.js.map +1 -1
  112. package/package.json +1 -1
  113. package/schemas/workflow-v1.schema.json +262 -1
@@ -0,0 +1,152 @@
1
+ import { addEvent, renderTemplate } from './runtime-helpers.js';
2
+ import { executeProviderAction } from './action-provider-adapters.js';
3
+ import { sanitizeActionInput, workflowActionProposalDigest } from './action-digest.js';
4
+ import { rememberExecutedAction, suppressDuplicateAction } from './action-execution-ledger.js';
5
+ import { actionPolicyReason } from './action-policy.js';
6
+ const MAX_RESPONSE_CHARS = 4_000;
7
+ export { WorkflowActionError } from './action-provider-adapters.js';
8
+ export async function executeActionAdapter(run, nodeId, node, options = {}) {
9
+ const idempotencyKey = await renderOptionalTemplate(run, node.idempotencyKey);
10
+ const actionInput = await renderActionInput(run, node.with ?? {});
11
+ const duplicate = suppressDuplicateAction(run, nodeId, node, idempotencyKey, actionInput);
12
+ if (duplicate)
13
+ return duplicate;
14
+ if (node.requiresApproval === true && options.approved !== true) {
15
+ return declaredAction(run, nodeId, node, 'awaiting_approval', idempotencyKey, actionInput);
16
+ }
17
+ if (node.adapter === 'webhook' || node.adapter === 'http') {
18
+ return executeWebhookAction(run, nodeId, node, idempotencyKey, actionInput);
19
+ }
20
+ const providerAction = await executeProviderAction(run, nodeId, node, actionInput, {
21
+ idempotencyKey,
22
+ });
23
+ if (providerAction)
24
+ return providerAction;
25
+ return declaredAction(run, nodeId, node, 'declared', idempotencyKey, actionInput);
26
+ }
27
+ async function executeWebhookAction(run, nodeId, node, idempotencyKey, actionInput) {
28
+ const url = stringValue(actionInput['url']);
29
+ if (!url)
30
+ return declaredAction(run, nodeId, node, 'missing_url', idempotencyKey, actionInput);
31
+ const method = stringValue(actionInput['method']) ?? actionMethod(node.action);
32
+ const headers = headerRecord(actionInput['headers']);
33
+ const body = actionInput['body'];
34
+ const response = await fetch(url, {
35
+ method,
36
+ headers: {
37
+ Accept: 'application/json, text/plain;q=0.9, */*;q=0.8',
38
+ ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
39
+ ...headers,
40
+ ...idempotencyHeader(headers, idempotencyKey),
41
+ },
42
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
43
+ });
44
+ const responseText = await safeResponseText(response);
45
+ const metadata = {
46
+ action: {
47
+ adapter: node.adapter,
48
+ action: node.action,
49
+ idempotencyKey: idempotencyKey ?? null,
50
+ requiresApproval: node.requiresApproval === true,
51
+ status: response.ok ? 'executed' : 'failed',
52
+ digest: workflowActionProposalDigest(node, { idempotencyKey, input: actionInput }),
53
+ input: sanitizeActionInput(actionInput),
54
+ request: { method, url },
55
+ response: {
56
+ status: response.status,
57
+ ok: response.ok,
58
+ bodyExcerpt: responseText.slice(0, MAX_RESPONSE_CHARS),
59
+ },
60
+ },
61
+ };
62
+ addEvent(run, response.ok ? 'action-executed' : 'action-failed', response.ok
63
+ ? `Action node ${nodeId} executed ${node.adapter}.${node.action}`
64
+ : `Action node ${nodeId} failed ${node.adapter}.${node.action}`, metadata, nodeId);
65
+ if (!response.ok) {
66
+ throw new Error(`Action ${nodeId} failed with HTTP ${response.status}: ${responseText}`);
67
+ }
68
+ rememberExecutedAction(run, nodeId, node, idempotencyKey, actionInput, {
69
+ output: `${node.adapter}.${node.action} ${response.status}`,
70
+ response: metadata.action.response,
71
+ });
72
+ return {
73
+ output: `${node.adapter}.${node.action} ${response.status}`,
74
+ metadata,
75
+ };
76
+ }
77
+ async function renderActionInput(run, value) {
78
+ return (await renderActionValue(run, value));
79
+ }
80
+ async function renderOptionalTemplate(run, value) {
81
+ if (!value)
82
+ return undefined;
83
+ const rendered = await renderTemplate(value, run);
84
+ return rendered.trim() === '' ? undefined : rendered;
85
+ }
86
+ async function renderActionValue(run, value) {
87
+ if (typeof value === 'string')
88
+ return await renderTemplate(value, run);
89
+ if (Array.isArray(value)) {
90
+ return await Promise.all(value.map((entry) => renderActionValue(run, entry)));
91
+ }
92
+ if (value && typeof value === 'object') {
93
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, entry]) => [
94
+ key,
95
+ await renderActionValue(run, entry),
96
+ ])));
97
+ }
98
+ return value;
99
+ }
100
+ function declaredAction(_run, _nodeId, node, status, idempotencyKey, actionInput) {
101
+ return {
102
+ output: `${node.adapter}.${node.action}`,
103
+ metadata: {
104
+ action: {
105
+ adapter: node.adapter,
106
+ action: node.action,
107
+ idempotencyKey: idempotencyKey ?? null,
108
+ requiresApproval: node.requiresApproval === true,
109
+ policyReason: actionPolicyReason(node),
110
+ status,
111
+ digest: workflowActionProposalDigest(node, { idempotencyKey, input: actionInput }),
112
+ input: sanitizeActionInput(actionInput),
113
+ },
114
+ },
115
+ };
116
+ }
117
+ function actionMethod(action) {
118
+ if (action === 'get')
119
+ return 'GET';
120
+ if (action === 'put')
121
+ return 'PUT';
122
+ if (action === 'patch')
123
+ return 'PATCH';
124
+ if (action === 'delete')
125
+ return 'DELETE';
126
+ return 'POST';
127
+ }
128
+ function idempotencyHeader(headers, idempotencyKey) {
129
+ if (!idempotencyKey)
130
+ return {};
131
+ const alreadySet = Object.keys(headers).some((key) => key.toLowerCase() === 'idempotency-key');
132
+ return alreadySet ? {} : { 'Idempotency-Key': idempotencyKey };
133
+ }
134
+ function headerRecord(value) {
135
+ if (!value || typeof value !== 'object' || Array.isArray(value))
136
+ return {};
137
+ return Object.fromEntries(Object.entries(value)
138
+ .filter((entry) => typeof entry[1] === 'string')
139
+ .map(([key, entry]) => [key, entry]));
140
+ }
141
+ function stringValue(value) {
142
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
143
+ }
144
+ async function safeResponseText(response) {
145
+ try {
146
+ return await response.text();
147
+ }
148
+ catch {
149
+ return '';
150
+ }
151
+ }
152
+ //# sourceMappingURL=action-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-adapters.js","sourceRoot":"","sources":["../../src/workflows/action-adapters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { WorkflowActionNode, WorkflowInputValue } from './types.js';
2
+ export interface WorkflowActionProposalDigestInput {
3
+ idempotencyKey?: string;
4
+ input?: Record<string, WorkflowInputValue>;
5
+ }
6
+ export declare function workflowActionProposalDigest(node: WorkflowActionNode, options?: WorkflowActionProposalDigestInput): string;
7
+ export declare function sanitizeActionInput(value: WorkflowInputValue | Record<string, WorkflowInputValue>): WorkflowInputValue | Record<string, WorkflowInputValue>;
8
+ //# sourceMappingURL=action-digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-digest.d.ts","sourceRoot":"","sources":["../../src/workflows/action-digest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE,MAAM,WAAW,iCAAiC;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC5C;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,kBAAkB,EACxB,OAAO,GAAE,iCAAsC,GAC9C,MAAM,CAYR;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAC7D,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAUzD"}
@@ -0,0 +1,36 @@
1
+ import { createHash } from 'node:crypto';
2
+ const SECRET_KEY_PATTERN = /(authorization|token|secret|password|api[_-]?key|private[_-]?key)/i;
3
+ export function workflowActionProposalDigest(node, options = {}) {
4
+ return `sha256:${createHash('sha256')
5
+ .update(stableJson({
6
+ adapter: node.adapter,
7
+ action: node.action,
8
+ idempotencyKey: options.idempotencyKey ?? null,
9
+ requiresApproval: node.requiresApproval === true,
10
+ input: sanitizeActionInput(options.input ?? {}),
11
+ }))
12
+ .digest('hex')}`;
13
+ }
14
+ export function sanitizeActionInput(value) {
15
+ if (Array.isArray(value))
16
+ return value.map((entry) => sanitizeActionInput(entry));
17
+ if (!value || typeof value !== 'object')
18
+ return value;
19
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
20
+ key,
21
+ SECRET_KEY_PATTERN.test(key) ? '[redacted]' : sanitizeActionInput(entry),
22
+ ]));
23
+ }
24
+ function stableJson(value) {
25
+ return JSON.stringify(sortKeys(value));
26
+ }
27
+ function sortKeys(value) {
28
+ if (Array.isArray(value))
29
+ return value.map((entry) => sortKeys(entry));
30
+ if (!value || typeof value !== 'object')
31
+ return value;
32
+ return Object.fromEntries(Object.entries(value)
33
+ .sort(([left], [right]) => left.localeCompare(right))
34
+ .map(([key, entry]) => [key, sortKeys(entry)]));
35
+ }
36
+ //# sourceMappingURL=action-digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-digest.js","sourceRoot":"","sources":["../../src/workflows/action-digest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,kBAAkB,GAAG,oEAAoE,CAAC;AAOhG,MAAM,UAAU,4BAA4B,CAC1C,IAAwB,EACxB,UAA6C,EAAE;IAE/C,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC;SAClC,MAAM,CACL,UAAU,CAAC;QACT,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI;QAC9C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,KAAK,IAAI;QAChD,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KAChD,CAAC,CACH;SACA,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,KAA8D;IAE9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QAC1C,GAAG;QACH,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC;KACzE,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SAC7C,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ActionResult } from './action-provider-adapters.js';
2
+ import type { WorkflowActionNode, WorkflowInputValue, WorkflowRunRecord } from './types.js';
3
+ export declare function suppressDuplicateAction(run: WorkflowRunRecord, nodeId: string, node: WorkflowActionNode, idempotencyKey: string | undefined, actionInput: Record<string, WorkflowInputValue>): ActionResult | null;
4
+ export declare function rememberExecutedAction(run: WorkflowRunRecord, nodeId: string, node: WorkflowActionNode, idempotencyKey: string | undefined, actionInput: Record<string, WorkflowInputValue>, execution: {
5
+ output: string;
6
+ response?: Record<string, unknown>;
7
+ }): void;
8
+ //# sourceMappingURL=action-execution-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-execution-ledger.d.ts","sourceRoot":"","sources":["../../src/workflows/action-execution-ledger.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5F,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,EACxB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAC9C,YAAY,GAAG,IAAI,CAwCrB;AAED,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,EACxB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAC/C,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAChE,IAAI,CAcN"}
@@ -0,0 +1,57 @@
1
+ import { addEvent } from './runtime-helpers.js';
2
+ import { sanitizeActionInput, workflowActionProposalDigest } from './action-digest.js';
3
+ import { actionPolicyReason } from './action-policy.js';
4
+ export function suppressDuplicateAction(run, nodeId, node, idempotencyKey, actionInput) {
5
+ const key = actionLedgerKey(idempotencyKey);
6
+ if (!key)
7
+ return null;
8
+ const digest = workflowActionProposalDigest(node, { idempotencyKey, input: actionInput });
9
+ const existing = run.actionLedger?.[key];
10
+ if (!existing)
11
+ return null;
12
+ if (existing.digest !== digest) {
13
+ throw new Error(`Action idempotency key '${idempotencyKey}' was already used with a different proposed action in this run.`);
14
+ }
15
+ const metadata = {
16
+ action: {
17
+ adapter: node.adapter,
18
+ action: node.action,
19
+ idempotencyKey,
20
+ requiresApproval: node.requiresApproval === true,
21
+ policyReason: actionPolicyReason(node),
22
+ status: 'already_executed',
23
+ digest,
24
+ input: sanitizeActionInput(actionInput),
25
+ duplicateOfNodeId: existing.nodeId,
26
+ executedAt: existing.executedAt,
27
+ response: existing.response,
28
+ },
29
+ };
30
+ addEvent(run, 'action-duplicate-suppressed', `Action node ${nodeId} reused idempotency key ${idempotencyKey}; prior side effect kept.`, metadata, nodeId);
31
+ return {
32
+ output: existing.output,
33
+ metadata,
34
+ };
35
+ }
36
+ export function rememberExecutedAction(run, nodeId, node, idempotencyKey, actionInput, execution) {
37
+ const key = actionLedgerKey(idempotencyKey);
38
+ if (!key || !idempotencyKey)
39
+ return;
40
+ run.actionLedger ??= {};
41
+ run.actionLedger[key] = {
42
+ nodeId,
43
+ adapter: node.adapter,
44
+ action: node.action,
45
+ idempotencyKey,
46
+ digest: workflowActionProposalDigest(node, { idempotencyKey, input: actionInput }),
47
+ output: execution.output,
48
+ executedAt: Date.now(),
49
+ ...(execution.response ? { response: execution.response } : {}),
50
+ };
51
+ }
52
+ function actionLedgerKey(idempotencyKey) {
53
+ if (!idempotencyKey)
54
+ return null;
55
+ return `idempotency:${idempotencyKey}`;
56
+ }
57
+ //# sourceMappingURL=action-execution-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-execution-ledger.js","sourceRoot":"","sources":["../../src/workflows/action-execution-ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAIxD,MAAM,UAAU,uBAAuB,CACrC,GAAsB,EACtB,MAAc,EACd,IAAwB,EACxB,cAAkC,EAClC,WAA+C;IAE/C,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,MAAM,GAAG,4BAA4B,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,2BAA2B,cAAc,kEAAkE,CAC5G,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG;QACf,MAAM,EAAE;YACN,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc;YACd,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,KAAK,IAAI;YAChD,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC;YACtC,MAAM,EAAE,kBAAkB;YAC1B,MAAM;YACN,KAAK,EAAE,mBAAmB,CAAC,WAAW,CAAC;YACvC,iBAAiB,EAAE,QAAQ,CAAC,MAAM;YAClC,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B;KACF,CAAC;IACF,QAAQ,CACN,GAAG,EACH,6BAA6B,EAC7B,eAAe,MAAM,2BAA2B,cAAc,2BAA2B,EACzF,QAAQ,EACR,MAAM,CACP,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,GAAsB,EACtB,MAAc,EACd,IAAwB,EACxB,cAAkC,EAClC,WAA+C,EAC/C,SAAiE;IAEjE,MAAM,GAAG,GAAG,eAAe,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc;QAAE,OAAO;IACpC,GAAG,CAAC,YAAY,KAAK,EAAE,CAAC;IACxB,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG;QACtB,MAAM;QACN,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc;QACd,MAAM,EAAE,4BAA4B,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAClF,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,cAAkC;IACzD,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,eAAe,cAAc,EAAE,CAAC;AACzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { WorkflowActionNode } from './types.js';
2
+ export declare function actionPolicyReason(node: WorkflowActionNode): string | null;
3
+ //# sourceMappingURL=action-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-policy.d.ts","sourceRoot":"","sources":["../../src/workflows/action-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAU1E"}
@@ -0,0 +1,13 @@
1
+ export function actionPolicyReason(node) {
2
+ const reason = node.policy?.reason?.trim();
3
+ if (reason)
4
+ return reason;
5
+ if (node.requiresApproval === true) {
6
+ return 'This side effect is configured to require human approval before execution.';
7
+ }
8
+ if (node.policy?.approvalRequired === true) {
9
+ return 'Workflow policy requires human approval before this side effect executes.';
10
+ }
11
+ return null;
12
+ }
13
+ //# sourceMappingURL=action-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-policy.js","sourceRoot":"","sources":["../../src/workflows/action-policy.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,kBAAkB,CAAC,IAAwB;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,IAAI,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;QACnC,OAAO,4EAA4E,CAAC;IACtF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC3C,OAAO,2EAA2E,CAAC;IACrF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { WorkflowActionNode, WorkflowInputValue, WorkflowRunRecord } from './types.js';
2
+ export interface ActionResult {
3
+ output: string;
4
+ metadata: Record<string, unknown>;
5
+ }
6
+ export declare class WorkflowActionError extends Error {
7
+ readonly result: ActionResult;
8
+ constructor(message: string, result: ActionResult);
9
+ }
10
+ export declare function executeProviderAction(run: WorkflowRunRecord, nodeId: string, node: WorkflowActionNode, actionInput: Record<string, WorkflowInputValue>, options?: {
11
+ idempotencyKey?: string;
12
+ }): Promise<ActionResult | null>;
13
+ //# sourceMappingURL=action-provider-adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-provider-adapters.d.ts","sourceRoot":"","sources":["../../src/workflows/action-provider-adapters.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAI5F,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAG1C,QAAQ,CAAC,MAAM,EAAE,YAAY;gBAD7B,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,YAAY;CAIhC;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,EACxB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAC/C,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAW9B"}
@@ -0,0 +1,284 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { addEvent } from './runtime-helpers.js';
3
+ import { rememberExecutedAction } from './action-execution-ledger.js';
4
+ import { sanitizeActionInput, workflowActionProposalDigest } from './action-digest.js';
5
+ import { actionPolicyReason } from './action-policy.js';
6
+ const MAX_RESPONSE_CHARS = 4_000;
7
+ export class WorkflowActionError extends Error {
8
+ result;
9
+ constructor(message, result) {
10
+ super(message);
11
+ this.result = result;
12
+ }
13
+ }
14
+ export async function executeProviderAction(run, nodeId, node, actionInput, options = {}) {
15
+ if (node.adapter === 'github') {
16
+ return executeGitHubAction(run, nodeId, node, actionInput, options);
17
+ }
18
+ if (node.adapter === 'jira') {
19
+ return executeJiraAction(run, nodeId, node, actionInput, options);
20
+ }
21
+ if (node.adapter === 'slack') {
22
+ return executeSlackAction(run, nodeId, node, actionInput, options);
23
+ }
24
+ return null;
25
+ }
26
+ async function executeGitHubAction(run, nodeId, node, actionInput, options) {
27
+ const owner = stringValue(actionInput['owner']);
28
+ const repo = stringValue(actionInput['repo']);
29
+ const token = stringValue(actionInput['token']) ?? process.env['GITHUB_TOKEN'];
30
+ if (!owner || !repo || !token) {
31
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
32
+ }
33
+ if (node.action === 'create_pr' || node.action === 'create_pull_request') {
34
+ return executeJsonApiAction(run, nodeId, node, {
35
+ method: 'POST',
36
+ url: `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls`,
37
+ headers: withIdempotencyHeader(githubHeaders(token), options.idempotencyKey),
38
+ proposalInput: actionInput,
39
+ body: {
40
+ title: stringValue(actionInput['title']) ?? 'Viewport workflow change',
41
+ head: stringValue(actionInput['head']) ?? stringValue(actionInput['branch']),
42
+ base: stringValue(actionInput['base']) ?? 'main',
43
+ body: stringValue(actionInput['body']),
44
+ draft: booleanValue(actionInput['draft']),
45
+ },
46
+ });
47
+ }
48
+ if (node.action === 'comment' || node.action === 'comment_issue') {
49
+ const issueNumber = stringValue(actionInput['issue_number']) ?? stringValue(actionInput['issueNumber']);
50
+ const body = stringValue(actionInput['body']);
51
+ if (!issueNumber || !body) {
52
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
53
+ }
54
+ return executeJsonApiAction(run, nodeId, node, {
55
+ method: 'POST',
56
+ url: `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${encodeURIComponent(issueNumber)}/comments`,
57
+ headers: withIdempotencyHeader(githubHeaders(token), options.idempotencyKey),
58
+ proposalInput: actionInput,
59
+ body: { body },
60
+ });
61
+ }
62
+ return declaredProviderAction(node, 'declared', options.idempotencyKey, actionInput);
63
+ }
64
+ async function executeJiraAction(run, nodeId, node, actionInput, options) {
65
+ const baseUrl = normalizedBaseUrl(stringValue(actionInput['base_url']) ??
66
+ stringValue(actionInput['baseUrl']) ??
67
+ process.env['JIRA_BASE_URL']);
68
+ const token = stringValue(actionInput['token']) ?? process.env['JIRA_API_TOKEN'];
69
+ const email = stringValue(actionInput['email']) ?? process.env['JIRA_EMAIL'];
70
+ const issueKey = stringValue(actionInput['issue_key']) ??
71
+ stringValue(actionInput['issueKey']) ??
72
+ stringValue(actionInput['key']);
73
+ if (!baseUrl || !token || !issueKey) {
74
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
75
+ }
76
+ if (node.action === 'comment' ||
77
+ node.action === 'comment_issue' ||
78
+ node.action === 'issue.comment') {
79
+ const body = stringValue(actionInput['body']);
80
+ if (!body)
81
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
82
+ return executeJsonApiAction(run, nodeId, node, {
83
+ method: 'POST',
84
+ url: `${baseUrl}/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment`,
85
+ headers: withIdempotencyHeader(jiraHeaders(token, email), options.idempotencyKey),
86
+ proposalInput: actionInput,
87
+ body: { body: jiraDocument(body) },
88
+ });
89
+ }
90
+ if (node.action === 'transition' || node.action === 'issue.transition') {
91
+ const transitionId = stringValue(actionInput['transition_id']) ?? stringValue(actionInput['transitionId']);
92
+ if (!transitionId)
93
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
94
+ return executeJsonApiAction(run, nodeId, node, {
95
+ method: 'POST',
96
+ url: `${baseUrl}/rest/api/3/issue/${encodeURIComponent(issueKey)}/transitions`,
97
+ headers: withIdempotencyHeader(jiraHeaders(token, email), options.idempotencyKey),
98
+ proposalInput: actionInput,
99
+ body: { transition: { id: transitionId } },
100
+ });
101
+ }
102
+ return declaredProviderAction(node, 'declared', options.idempotencyKey, actionInput);
103
+ }
104
+ async function executeSlackAction(run, nodeId, node, actionInput, options) {
105
+ const token = stringValue(actionInput['token']) ?? process.env['SLACK_BOT_TOKEN'];
106
+ const channel = stringValue(actionInput['channel']);
107
+ const text = stringValue(actionInput['text']) ?? stringValue(actionInput['body']);
108
+ if (!token || !channel || !text) {
109
+ return declaredProviderAction(node, 'missing_url', options.idempotencyKey, actionInput);
110
+ }
111
+ if (node.action === 'post_message' ||
112
+ node.action === 'message' ||
113
+ node.action === 'chat.postMessage') {
114
+ return executeJsonApiAction(run, nodeId, node, {
115
+ method: 'POST',
116
+ url: 'https://slack.com/api/chat.postMessage',
117
+ headers: withIdempotencyHeader({ Authorization: `Bearer ${token}` }, options.idempotencyKey),
118
+ proposalInput: actionInput,
119
+ body: {
120
+ channel,
121
+ text,
122
+ client_msg_id: options.idempotencyKey,
123
+ thread_ts: stringValue(actionInput['thread_ts']) ?? stringValue(actionInput['threadTs']),
124
+ },
125
+ okFromBody: true,
126
+ });
127
+ }
128
+ return declaredProviderAction(node, 'declared', options.idempotencyKey, actionInput);
129
+ }
130
+ async function executeJsonApiAction(run, nodeId, node, request) {
131
+ const response = await fetch(request.url, {
132
+ method: request.method,
133
+ headers: {
134
+ Accept: 'application/vnd.github+json, application/json;q=0.9, */*;q=0.8',
135
+ 'Content-Type': 'application/json',
136
+ ...request.headers,
137
+ },
138
+ body: JSON.stringify(compactObject(request.body)),
139
+ });
140
+ const responseText = await safeResponseText(response);
141
+ const parsed = parseJson(responseText);
142
+ const appOk = request.okFromBody ? objectBoolean(parsed, 'ok') !== false : true;
143
+ const ok = response.ok && appOk;
144
+ const metadata = {
145
+ action: {
146
+ adapter: node.adapter,
147
+ action: node.action,
148
+ idempotencyKey: idempotencyKeyFromHeaders(request.headers) ?? null,
149
+ requiresApproval: node.requiresApproval === true,
150
+ policyReason: actionPolicyReason(node),
151
+ status: ok ? 'executed' : 'failed',
152
+ digest: workflowActionProposalDigest(node, {
153
+ idempotencyKey: idempotencyKeyFromHeaders(request.headers),
154
+ input: request.proposalInput,
155
+ }),
156
+ input: sanitizeActionInput(request.proposalInput),
157
+ request: { method: request.method, url: request.url },
158
+ response: {
159
+ status: response.status,
160
+ ok,
161
+ bodyExcerpt: responseText.slice(0, MAX_RESPONSE_CHARS),
162
+ htmlUrl: objectString(parsed, 'html_url'),
163
+ apiUrl: objectString(parsed, 'url'),
164
+ number: objectNumber(parsed, 'number'),
165
+ channel: objectString(parsed, 'channel'),
166
+ ts: objectString(parsed, 'ts'),
167
+ error: objectString(parsed, 'error'),
168
+ },
169
+ },
170
+ };
171
+ addEvent(run, ok ? 'action-executed' : 'action-failed', ok
172
+ ? `Action node ${nodeId} executed ${node.adapter}.${node.action}`
173
+ : `Action node ${nodeId} failed ${node.adapter}.${node.action}`, metadata, nodeId);
174
+ if (!ok) {
175
+ throw new WorkflowActionError(`Action ${nodeId} failed with HTTP ${response.status}: ${responseText}`, {
176
+ output: `${node.adapter}.${node.action} ${response.status}`,
177
+ metadata,
178
+ });
179
+ }
180
+ rememberExecutedAction(run, nodeId, node, idempotencyKeyFromHeaders(request.headers), request.proposalInput, {
181
+ output: `${node.adapter}.${node.action} ${response.status}`,
182
+ response: metadata.action.response,
183
+ });
184
+ return {
185
+ output: `${node.adapter}.${node.action} ${response.status}`,
186
+ metadata,
187
+ };
188
+ }
189
+ function declaredProviderAction(node, status, idempotencyKey, actionInput) {
190
+ return {
191
+ output: `${node.adapter}.${node.action}`,
192
+ metadata: {
193
+ action: {
194
+ adapter: node.adapter,
195
+ action: node.action,
196
+ idempotencyKey: idempotencyKey ?? null,
197
+ requiresApproval: node.requiresApproval === true,
198
+ policyReason: actionPolicyReason(node),
199
+ status,
200
+ digest: workflowActionProposalDigest(node, {
201
+ idempotencyKey,
202
+ input: actionInput,
203
+ }),
204
+ input: sanitizeActionInput(actionInput),
205
+ },
206
+ },
207
+ };
208
+ }
209
+ function withIdempotencyHeader(headers, idempotencyKey) {
210
+ if (!idempotencyKey)
211
+ return headers;
212
+ const alreadySet = Object.keys(headers).some((key) => key.toLowerCase() === 'idempotency-key');
213
+ return alreadySet ? headers : { ...headers, 'Idempotency-Key': idempotencyKey };
214
+ }
215
+ function idempotencyKeyFromHeaders(headers) {
216
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === 'idempotency-key');
217
+ return entry?.[1];
218
+ }
219
+ function githubHeaders(token) {
220
+ return {
221
+ Authorization: `Bearer ${token}`,
222
+ 'X-GitHub-Api-Version': '2022-11-28',
223
+ };
224
+ }
225
+ function jiraHeaders(token, email) {
226
+ if (email) {
227
+ return { Authorization: `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}` };
228
+ }
229
+ return { Authorization: `Bearer ${token}` };
230
+ }
231
+ function jiraDocument(text) {
232
+ return {
233
+ type: 'doc',
234
+ version: 1,
235
+ content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
236
+ };
237
+ }
238
+ function normalizedBaseUrl(value) {
239
+ return value ? value.replace(/\/+$/, '') : undefined;
240
+ }
241
+ function booleanValue(value) {
242
+ return typeof value === 'boolean' ? value : undefined;
243
+ }
244
+ function compactObject(value) {
245
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined));
246
+ }
247
+ function parseJson(value) {
248
+ try {
249
+ return JSON.parse(value);
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
255
+ function objectString(value, key) {
256
+ if (!value || typeof value !== 'object' || Array.isArray(value))
257
+ return null;
258
+ const entry = value[key];
259
+ return typeof entry === 'string' ? entry : null;
260
+ }
261
+ function objectNumber(value, key) {
262
+ if (!value || typeof value !== 'object' || Array.isArray(value))
263
+ return null;
264
+ const entry = value[key];
265
+ return typeof entry === 'number' ? entry : null;
266
+ }
267
+ function objectBoolean(value, key) {
268
+ if (!value || typeof value !== 'object' || Array.isArray(value))
269
+ return null;
270
+ const entry = value[key];
271
+ return typeof entry === 'boolean' ? entry : null;
272
+ }
273
+ function stringValue(value) {
274
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
275
+ }
276
+ async function safeResponseText(response) {
277
+ try {
278
+ return await response.text();
279
+ }
280
+ catch {
281
+ return '';
282
+ }
283
+ }
284
+ //# sourceMappingURL=action-provider-adapters.js.map