@codemation/core-nodes 0.13.0 → 0.14.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 (73) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.cjs +34 -433
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +95 -1312
  5. package/dist/index.d.ts +95 -1312
  6. package/dist/index.js +35 -435
  7. package/dist/index.js.map +1 -1
  8. package/dist/metadata.json +1 -1
  9. package/package.json +2 -2
  10. package/src/authoring/defineRestNode.types.ts +0 -84
  11. package/src/canvasIconName.ts +0 -7
  12. package/src/chatModels/CodemationChatModelConfig.ts +0 -10
  13. package/src/chatModels/CodemationChatModelFactory.ts +0 -7
  14. package/src/chatModels/ManagedHmacSignerFactory.types.ts +0 -35
  15. package/src/chatModels/OpenAIChatModelFactory.ts +0 -2
  16. package/src/chatModels/OpenAiChatModelPresetsFactory.ts +0 -5
  17. package/src/chatModels/OpenAiCredentialSession.ts +0 -1
  18. package/src/chatModels/OpenAiStrictJsonSchemaFactory.ts +0 -21
  19. package/src/credentials/ApiKeyCredentialType.ts +0 -3
  20. package/src/credentials/BasicAuthCredentialType.ts +0 -4
  21. package/src/credentials/BearerTokenCredentialType.ts +0 -4
  22. package/src/credentials/OAuth2ClientCredentialsTypeFactory.ts +0 -19
  23. package/src/credentials/OAuth2TokenExchangeFactory.ts +0 -7
  24. package/src/http/HttpBodyBuilder.ts +0 -16
  25. package/src/http/HttpRequestExecutor.ts +0 -35
  26. package/src/http/HttpUrlBuilder.ts +0 -4
  27. package/src/http/SSRFBlockedError.ts +0 -4
  28. package/src/http/SsrfGuard.ts +10 -50
  29. package/src/http/httpRequest.types.ts +0 -49
  30. package/src/index.ts +1 -0
  31. package/src/nodes/AIAgentConfig.ts +0 -44
  32. package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
  33. package/src/nodes/AIAgentNode.ts +0 -132
  34. package/src/nodes/AgentBinaryContentFactory.ts +0 -12
  35. package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
  36. package/src/nodes/AgentMessageFactory.ts +0 -18
  37. package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
  38. package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
  39. package/src/nodes/AgentToolResultContentFactory.ts +0 -29
  40. package/src/nodes/AssertionNode.ts +0 -14
  41. package/src/nodes/BM25Index.ts +0 -14
  42. package/src/nodes/ConnectionCredentialExecutionContextFactory.ts +0 -5
  43. package/src/nodes/ConnectionCredentialNode.ts +0 -4
  44. package/src/nodes/CronTriggerFactory.ts +0 -9
  45. package/src/nodes/DeferredMetaToolStrategy.ts +0 -18
  46. package/src/nodes/DeferredMetaToolStrategyFactory.ts +0 -5
  47. package/src/nodes/HttpRequestNodeFactory.ts +0 -14
  48. package/src/nodes/InboxApprovalNode.types.ts +0 -16
  49. package/src/nodes/IsTestRunNode.ts +0 -8
  50. package/src/nodes/ManualTriggerFactory.ts +0 -3
  51. package/src/nodes/ManualTriggerNode.ts +0 -4
  52. package/src/nodes/MergeNode.ts +0 -1
  53. package/src/nodes/NodeBackedToolRuntime.ts +0 -14
  54. package/src/nodes/SubWorkflowNode.ts +0 -3
  55. package/src/nodes/SwitchNode.ts +0 -3
  56. package/src/nodes/TestTriggerNode.ts +0 -9
  57. package/src/nodes/aiAgentSupport.types.ts +0 -16
  58. package/src/nodes/assertion.ts +0 -10
  59. package/src/nodes/codemationDocumentScannerNode.ts +0 -18
  60. package/src/nodes/collections/collectionListNode.types.ts +0 -1
  61. package/src/nodes/httpRequest.ts +0 -68
  62. package/src/nodes/isTestRun.ts +0 -4
  63. package/src/nodes/mapData.ts +0 -1
  64. package/src/nodes/merge.ts +0 -4
  65. package/src/nodes/mergeExecutionUtils.types.ts +0 -3
  66. package/src/nodes/nodeOptions.types.ts +0 -8
  67. package/src/nodes/schedulePollingTrigger.ts +37 -0
  68. package/src/nodes/split.ts +0 -4
  69. package/src/nodes/testTrigger.ts +0 -21
  70. package/src/nodes/wait.ts +0 -1
  71. package/src/nodes/webhookTriggerNode.ts +0 -5
  72. package/src/register.types.ts +0 -10
  73. package/src/workflows/AIAgentConnectionWorkflowExpander.ts +0 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "@codemation/core-nodes",
4
- "packageVersion": "0.13.0",
4
+ "packageVersion": "0.14.0",
5
5
  "description": "",
6
6
  "kind": "nodes",
7
7
  "nodes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemation/core-nodes",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,7 +33,7 @@
33
33
  "@ai-sdk/provider": "^3.0.8",
34
34
  "ai": "^6.0.168",
35
35
  "croner": "^10.0.1",
36
- "@codemation/core": "0.14.0"
36
+ "@codemation/core": "0.15.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^25.3.5",
@@ -9,27 +9,12 @@ import { SsrfGuard } from "../http/SsrfGuard";
9
9
 
10
10
  type MaybePromise<T> = T | Promise<T>;
11
11
 
12
- /**
13
- * API endpoint descriptor.
14
- */
15
12
  export type RestNodeApi = Readonly<{
16
- /**
17
- * Base URL, e.g. `"https://api.slack.com"`.
18
- */
19
13
  baseUrl: string;
20
- /**
21
- * Path relative to `baseUrl`. May contain `{paramName}` placeholders that
22
- * are substituted from `input` keys before the request is made.
23
- * Example: `"/users/{userId}/profile"`
24
- */
25
14
  path: string;
26
- /** HTTP method (default: GET). */
27
15
  method?: string;
28
16
  }>;
29
17
 
30
- /**
31
- * The HTTP result shape passed into the `response` mapper.
32
- */
33
18
  export type RestNodeResponseContext = Readonly<{
34
19
  status: number;
35
20
  ok: boolean;
@@ -40,25 +25,13 @@ export type RestNodeResponseContext = Readonly<{
40
25
  text?: string;
41
26
  }>;
42
27
 
43
- /**
44
- * What the `request` callback may return to customise the request.
45
- */
46
28
  export type RestNodeRequestShape = Readonly<{
47
- /** Additional path parameters to substitute (merged with `input`). */
48
29
  pathParams?: Readonly<Record<string, string>>;
49
- /** Extra query params. */
50
30
  query?: Readonly<Record<string, string>>;
51
- /** Extra headers. */
52
31
  headers?: Readonly<Record<string, string>>;
53
- /** Request body. */
54
32
  body?: HttpBodySpec;
55
33
  }>;
56
34
 
57
- /**
58
- * Error handling policy for non-2xx responses.
59
- * - `"throw"` (default) — throws an `Error` for non-2xx responses.
60
- * - `"passthrough"` — returns the result regardless of status.
61
- */
62
35
  export type RestNodeErrorPolicy = "throw" | "passthrough";
63
36
 
64
37
  export interface DefineRestNodeOptions<
@@ -72,44 +45,16 @@ export interface DefineRestNodeOptions<
72
45
  readonly description?: string;
73
46
  readonly icon?: string;
74
47
  readonly api: RestNodeApi;
75
- /**
76
- * Credential bindings keyed by slot. Use the built-in credential types from
77
- * `@codemation/core-nodes` (e.g. `bearerTokenCredentialType`) or any custom one.
78
- * The slot key must match what the `request` callback's context uses.
79
- */
80
48
  readonly credentials?: TCredentials;
81
- /**
82
- * Zod schema for per-item input. Validated before `execute`.
83
- */
84
49
  readonly inputSchema?: ZodType<TInputJson>;
85
- /**
86
- * Builds the per-request customisations from the item input.
87
- * Return `body`, `query`, `headers`, and/or `pathParams`.
88
- */
89
50
  request?(context: Readonly<{ input: TInputJson }>): MaybePromise<RestNodeRequestShape>;
90
- /**
91
- * Maps the HTTP response to the node's output JSON.
92
- * When omitted, the output is `{ status, ok, statusText, mimeType, headers, json, text }`.
93
- */
94
51
  response?(context: RestNodeResponseContext & Readonly<{ input: TInputJson }>): MaybePromise<TOutputJson>;
95
- /**
96
- * How to handle non-2xx responses.
97
- * @default "throw"
98
- */
99
52
  readonly errorPolicy?: RestNodeErrorPolicy;
100
- /**
101
- * Static configuration summary surfaced in the workflow inspector.
102
- * Receives the static config (empty record for defineRestNode — config lives on item input).
103
- * Most callers return rows based on the static `api` descriptor instead.
104
- */
105
53
  readonly inspectorSummary?: (
106
54
  args: Readonly<{ config: Record<string, never> }>,
107
55
  ) => ReadonlyArray<NodeInspectorSummaryRow> | undefined;
108
56
  }
109
57
 
110
- /**
111
- * Substitutes `{name}` placeholders in a path template using values from `params`.
112
- */
113
58
  function substitutePath(template: string, params: Readonly<Record<string, unknown>>): string {
114
59
  return template.replace(/\{([^}]+)}/g, (_match, key: string) => {
115
60
  const value = params[key];
@@ -117,30 +62,6 @@ function substitutePath(template: string, params: Readonly<Record<string, unknow
117
62
  });
118
63
  }
119
64
 
120
- /**
121
- * Declarative helper for creating thin API-wrapper nodes.
122
- *
123
- * Usage:
124
- * ```ts
125
- * export const postMessage = defineRestNode({
126
- * key: "slack.post-message",
127
- * title: "Send Slack message",
128
- * icon: "si:slack",
129
- * api: { baseUrl: "https://slack.com/api", path: "/chat.postMessage", method: "POST" },
130
- * credentials: { auth: bearerTokenCredentialType },
131
- * inputSchema: z.object({ channel: z.string(), text: z.string() }),
132
- * request: ({ input }) => ({
133
- * body: { kind: "json", data: { channel: input.channel, text: input.text } },
134
- * }),
135
- * response: ({ json }) => ({ messageTs: (json as any).ts }),
136
- * });
137
- * ```
138
- *
139
- * - `defineRestNode` is a thin wrapper over `defineNode`; it does not introduce a new runtime kind.
140
- * - Credential sessions are resolved via the `credentials` binding map (same as `defineNode`).
141
- * - Path `{placeholder}` substitution is applied from `input` keys before the request is made.
142
- * - Non-2xx responses throw an `Error` by default (`errorPolicy: "throw"`).
143
- */
144
65
  export function defineRestNode<
145
66
  TKey extends string,
146
67
  TCredentials extends DefinedNodeCredentialBindings | undefined,
@@ -160,13 +81,11 @@ export function defineRestNode<
160
81
  inputSchema: options.inputSchema,
161
82
  inspectorSummary: options.inspectorSummary,
162
83
  async execute({ input, item, ctx }, { credentials }) {
163
- // Resolve credential if one is bound.
164
84
  const credentialSlot = options.credentials ? Object.keys(options.credentials)[0] : undefined;
165
85
  const credential = credentialSlot
166
86
  ? await (credentials as Record<string, () => Promise<unknown>>)[credentialSlot]?.()
167
87
  : undefined;
168
88
 
169
- // Build path by substituting `{name}` placeholders from input.
170
89
  const inputRecord = (input as Record<string, unknown>) ?? {};
171
90
  const requestShape = options.request ? await options.request({ input }) : {};
172
91
  const pathParams = { ...inputRecord, ...(requestShape.pathParams ?? {}) };
@@ -210,9 +129,6 @@ export function defineRestNode<
210
129
  return await options.response({ ...responseCtx, input });
211
130
  }
212
131
 
213
- // Wrap in `{ json: ... }` so the engine's Item-shape detection unwraps once
214
- // and the response context becomes the item's payload as-is (preserving the
215
- // inner `json` field on the response for callers).
216
132
  return { json: responseCtx } as unknown as TOutputJson;
217
133
  },
218
134
  }) as unknown as DefinedNode<TKey, Record<string, never>, TInputJson, TOutputJson, TCredentials>;
@@ -1,8 +1 @@
1
- /**
2
- * Canvas / agent presentation:
3
- * - Lucide: `lucide:<kebab-name>` or legacy kebab name
4
- * - Built-in brand SVGs: `builtin:<id>` (host resolves to shipped SVG assets under `public/canvas-icons/builtin/`)
5
- * - Simple Icons: `si:<slug>` (host cherry-picks from `simple-icons`) or builtin asset when slug matches
6
- * - Image URLs: `http(s):`, `data:`, `/…`
7
- */
8
1
  export type CanvasIconName = string;
@@ -3,14 +3,6 @@ import type { AgentCanvasPresentation, ChatModelConfig } from "@codemation/core"
3
3
  import type { CanvasIconName } from "../canvasIconName";
4
4
  import { CodemationChatModelFactory } from "./CodemationChatModelFactory";
5
5
 
6
- /**
7
- * Complexity token sent to the managed LLM broker.
8
- * The broker maps this to a concrete provider model and thinking effort.
9
- * low = cheapest/fastest (short classification, simple extraction)
10
- * medium = default for most extraction/agent work
11
- * high = complex multi-step reasoning
12
- * xhigh = hardest problems, most capable model
13
- */
14
6
  export type ManagedComplexity = "low" | "medium" | "high" | "xhigh";
15
7
 
16
8
  export class CodemationChatModelConfig implements ChatModelConfig {
@@ -30,6 +22,4 @@ export class CodemationChatModelConfig implements ChatModelConfig {
30
22
  this.modelName = complexity;
31
23
  this.presentation = presentationIn ?? { icon: "lucide:bot", label: name };
32
24
  }
33
-
34
- // No getCredentialRequirements() — authentication is implicit via workspace pairing secret.
35
25
  }
@@ -9,7 +9,6 @@ export class CodemationChatModelFactory implements ChatModelFactory<CodemationCh
9
9
  async create(
10
10
  args: Readonly<{ config: CodemationChatModelConfig; ctx: NodeExecutionContext<any> }>,
11
11
  ): Promise<ChatLanguageModel> {
12
- // D5: read at session-create time so unpairing or misconfiguration surfaces at workflow run, not boot.
13
12
  // eslint-disable-next-line no-restricted-properties -- LLM_GATEWAY_URL is injected by the control-plane provisioner into the workspace process env; reading it at factory-create time is the justified boundary.
14
13
  const gatewayUrl = process.env["LLM_GATEWAY_URL"];
15
14
  if (!gatewayUrl) {
@@ -25,13 +24,7 @@ export class CodemationChatModelFactory implements ChatModelFactory<CodemationCh
25
24
  }
26
25
 
27
26
  const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
28
- // Lazy import: pulls @ai-sdk/anthropic + the `ai` SDK (~28MB RSS) only when a
29
- // chat model is actually built. Non-AI workflows never load it.
30
- // Using the Anthropic-native route so the broker's injected `thinking` /
31
- // `output_config.effort` fields survive (they are stripped by the OpenAI-compat route).
32
27
  const { createAnthropic } = await import("@ai-sdk/anthropic");
33
- // apiKey is required by the AI SDK but unused — authentication is handled by the HMAC-signed fetch wrapper.
34
- // baseURL: the SDK appends /messages → hits the broker's /v1/messages Anthropic-native route.
35
28
  const provider = createAnthropic({ baseURL: `${gatewayUrl}/v1`, apiKey: "codemation-managed", fetch: hmacFetch });
36
29
  const languageModel = provider(args.config.complexity);
37
30
 
@@ -1,44 +1,16 @@
1
1
  import { createHmac, createHash, randomBytes } from "node:crypto";
2
2
 
3
3
  export interface ManagedHmacSignerOptions {
4
- /**
5
- * When true (the default), the signer hashes the request body and includes
6
- * the hash in the HMAC base string. Use for small JSON payloads (LLM chat).
7
- *
8
- * When false (doc-scanner / LD11 mode), the signer computes the signature
9
- * over sha256("") regardless of the actual body bytes, and forwards those
10
- * bytes to the upstream fetch untouched. The HMAC binds the workspace
11
- * identity, not the file content — enabling streaming without buffering.
12
- */
13
4
  signBody?: boolean;
14
- /** Override wall-clock seconds for deterministic testing. */
15
5
  now?: () => number;
16
- /** Override nonce generation for deterministic testing. */
17
6
  nonce?: () => string;
18
7
  }
19
8
 
20
- /**
21
- * Creates an HMAC-signing fetch wrapper that authenticates requests to
22
- * Codemation managed services (LLM broker, doc-scanner) with the
23
- * Codemation-Hmac v=1 scheme.
24
- *
25
- * Mirrors HmacRequestSigner from @codemation/host/pairing without importing
26
- * that package (which would create a circular dependency since @codemation/host
27
- * depends on @codemation/core-nodes).
28
- *
29
- * @param workspaceId - Workspace identifier injected by the CP provisioner.
30
- * @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
31
- * @param options - Optional behaviour flags and test seams.
32
- */
33
9
  export function managedHmacFetchFactory(
34
10
  workspaceId: string,
35
11
  pairingSecret: string,
36
12
  options?: ManagedHmacSignerOptions,
37
13
  ): typeof fetch {
38
- // LLM chat (the existing caller) signs its small JSON body → signBody defaults true.
39
- // The doc-scanner node passes signBody:false (LD11): the HMAC binds the WORKSPACE,
40
- // not the file, so we never read/normalise the (possibly large, binary) body —
41
- // it is forwarded untouched and the signature covers an empty body hash.
42
14
  const signBody = options?.signBody ?? true;
43
15
 
44
16
  return async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
@@ -55,18 +27,11 @@ export function managedHmacFetchFactory(
55
27
  const headers = new Headers(init?.headers as Record<string, string> | undefined);
56
28
  headers.set("Authorization", authHeader);
57
29
 
58
- // When signing the body, forward the (possibly normalised) string.
59
- // When not signing (signBody:false), forward the ORIGINAL body untouched
60
- // so binary/streamed payloads are never buffered or re-encoded.
61
30
  const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
62
31
  return fetch(input, { ...init, body: outgoingBody, headers });
63
32
  };
64
33
  }
65
34
 
66
- /**
67
- * Produces a Codemation-Hmac v=1 Authorization header value.
68
- * Algorithm must match HmacVerifier.computeSignature() in the control-plane.
69
- */
70
35
  function buildHmacAuthHeader(
71
36
  workspaceId: string,
72
37
  pairingSecret: string,
@@ -10,8 +10,6 @@ export class OpenAIChatModelFactory implements ChatModelFactory<OpenAIChatModelC
10
10
  args: Readonly<{ config: OpenAIChatModelConfig; ctx: NodeExecutionContext<any> }>,
11
11
  ): Promise<ChatLanguageModel> {
12
12
  const session = await args.ctx.getCredential<OpenAiCredentialSession>(args.config.credentialSlotKey);
13
- // Lazy import: pulls @ai-sdk/openai + the `ai` SDK (~28MB RSS) only when a
14
- // chat model is actually built. Non-AI workflows never load it.
15
13
  const { createOpenAI } = await import("@ai-sdk/openai");
16
14
  const provider = createOpenAI({
17
15
  apiKey: session.apiKey,
@@ -1,10 +1,5 @@
1
1
  import { OpenAIChatModelConfig } from "./openAiChatModelConfig";
2
2
 
3
- /**
4
- * Default OpenAI chat model configs for scaffolds and demos (icon + label match {@link OpenAIChatModelConfig} defaults).
5
- * Prefer importing {@link openAiChatModelPresets} from here or from the consumer template re-export
6
- * instead of repeating {@link OpenAIChatModelConfig} construction in app workflows.
7
- */
8
3
  export class OpenAiChatModelPresets {
9
4
  readonly demoGpt4oMini = new OpenAIChatModelConfig("OpenAI", "gpt-4o-mini");
10
5
 
@@ -1,4 +1,3 @@
1
- /** Resolved credential session for OpenAI-compatible chat models (API key + optional custom base URL). */
2
1
  export type OpenAiCredentialSession = Readonly<{
3
2
  apiKey: string;
4
3
  baseUrl?: string;
@@ -3,27 +3,6 @@ import { inject, injectable } from "@codemation/core";
3
3
 
4
4
  import { AIAgentExecutionHelpersFactory } from "../nodes/AIAgentExecutionHelpersFactory";
5
5
 
6
- /**
7
- * Produces an OpenAI **strict mode**–compliant JSON Schema for an AIAgent `outputSchema`.
8
- *
9
- * Why this exists: AI SDK's default Zod → JSON Schema conversion (Zod v4's `toJSONSchema`) can
10
- * emit `unevaluatedProperties: false` or skip `additionalProperties: false` on object branches.
11
- * OpenAI's strict-mode validator rejects anything missing `additionalProperties: false` at
12
- * `context=()` (the root) and requires **all properties** in `required`. We convert here so all
13
- * legal Zod root shapes work (object, union, discriminated union, nullable-object wrapper, array,
14
- * intersection, …) and hand AI SDK a pre-tagged `jsonSchema(...)` record that passes straight
15
- * through to the provider.
16
- *
17
- * Rules enforced on the produced JSON Schema record:
18
- * - Every `type: "object"` node (root and nested under `allOf`/`anyOf`/`oneOf`/`items`/`prefixItems`/`$defs`):
19
- * - `additionalProperties: false`
20
- * - `required` lists **every** key in `properties` (OpenAI strict requires all properties required;
21
- * express optionality via `.nullable()` / `z.union([..., z.null()])`).
22
- * - `properties` is always an object (empty object allowed).
23
- * - `$schema`, `unevaluatedProperties`, and `default` are stripped (OpenAI rejects / ignores them).
24
- * - `sanitizeJsonSchemaRequiredKeywordsForCfworker` invariants from
25
- * {@link AIAgentExecutionHelpersFactory.createJsonSchemaRecord} are preserved as a starting point.
26
- */
27
6
  @injectable()
28
7
  export class OpenAiStrictJsonSchemaFactory {
29
8
  constructor(
@@ -1,9 +1,6 @@
1
1
  import { defineCredential } from "@codemation/core";
2
2
  import type { CredentialSession, HttpCredentialDelta } from "../http/httpRequest.types";
3
3
 
4
- /**
5
- * API key credential that injects a key either as an HTTP header or a query parameter.
6
- */
7
4
  export const apiKeyCredentialType = defineCredential({
8
5
  key: "core-nodes.api-key",
9
6
  label: "API Key",
@@ -1,10 +1,6 @@
1
1
  import { defineCredential } from "@codemation/core";
2
2
  import type { CredentialSession, HttpCredentialDelta } from "../http/httpRequest.types";
3
3
 
4
- /**
5
- * HTTP Basic authentication credential.
6
- * Session sets `Authorization: Basic <base64(username:password)>`.
7
- */
8
4
  export const basicAuthCredentialType = defineCredential({
9
5
  key: "core-nodes.basic-auth",
10
6
  label: "Basic Auth",
@@ -1,10 +1,6 @@
1
1
  import { defineCredential } from "@codemation/core";
2
2
  import type { CredentialSession, HttpCredentialDelta } from "../http/httpRequest.types";
3
3
 
4
- /**
5
- * Simple Bearer token credential.
6
- * Session sets `Authorization: Bearer <token>` on every request.
7
- */
8
4
  export const bearerTokenCredentialType = defineCredential({
9
5
  key: "core-nodes.bearer-token",
10
6
  label: "Bearer Token",
@@ -2,24 +2,6 @@ import { defineCredential } from "@codemation/core";
2
2
  import type { CredentialSession, HttpCredentialDelta } from "../http/httpRequest.types";
3
3
  import { OAuth2TokenExchangeFactory } from "./OAuth2TokenExchangeFactory";
4
4
 
5
- /**
6
- * OAuth2 client-credentials flow credential.
7
- *
8
- * This is a machine-to-machine flow: no user redirect occurs. The session
9
- * POSTs to the configured `tokenUrl` with `client_credentials` grant, caches
10
- * the resulting access token for the duration of the session, and injects it
11
- * as `Authorization: Bearer <token>` on each request.
12
- *
13
- * Token caching is per-session only (one createSession call = one token fetch
14
- * at most). Cross-session caching would require host-level state and is out of
15
- * scope here. Because the engine creates a fresh session per execution, a new
16
- * token is fetched once per node activation.
17
- *
18
- * NOTE: `auth` is intentionally omitted from the definition. The OAuth2
19
- * `auth: { kind: "oauth2" }` shape signals an authorization-code / user-redirect
20
- * flow; using it here would cause the host UI to render an OAuth consent button
21
- * that goes nowhere. Client-credentials is a purely server-side flow.
22
- */
23
5
  export const oauth2ClientCredentialsType = defineCredential({
24
6
  key: "core-nodes.oauth2-client-credentials",
25
7
  label: "OAuth2 Client Credentials",
@@ -65,7 +47,6 @@ export const oauth2ClientCredentialsType = defineCredential({
65
47
  throw new Error("OAuth2 client credentials are incomplete: tokenUrl, clientId, and clientSecret are required.");
66
48
  }
67
49
 
68
- // Fetch the token eagerly so any failure surfaces at session creation time.
69
50
  const accessToken = await new OAuth2TokenExchangeFactory().create({
70
51
  tokenUrl,
71
52
  clientId,
@@ -1,10 +1,3 @@
1
- /**
2
- * Performs an OAuth2 `client_credentials` token exchange against a token endpoint
3
- * and returns the resulting access token.
4
- *
5
- * Lives in a Factory file so the body URLSearchParams construction is allowed at
6
- * the composition root.
7
- */
8
1
  export type OAuth2ClientCredentialsArgs = Readonly<{
9
2
  tokenUrl: string;
10
3
  clientId: string;
@@ -6,17 +6,9 @@ import type { HttpBodySpec } from "./httpRequest.types";
6
6
 
7
7
  export type EncodedBody = Readonly<{
8
8
  body: NonNullable<RequestInit["body"]>;
9
- /**
10
- * Desired Content-Type header. Empty string means `fetch` should set it automatically
11
- * (used for multipart/form-data so the boundary is set correctly by the browser/Node runtime).
12
- */
13
9
  contentType: string;
14
10
  }>;
15
11
 
16
- /**
17
- * Builds a fetch-compatible `BodyInit` + Content-Type pair from an {@link HttpBodySpec}.
18
- * Multipart binaries are read from `item.binary` via `ctx.binary.openReadStream`.
19
- */
20
12
  export class HttpBodyBuilder {
21
13
  async build(
22
14
  spec: HttpBodySpec | undefined,
@@ -28,9 +20,6 @@ export class HttpBodyBuilder {
28
20
  }
29
21
 
30
22
  if (spec.kind === "json") {
31
- // Backstop for callers that reach this with a string despite the `data: object`
32
- // type (e.g. via `unknown`/`as`, or a resolved expression): re-stringifying it
33
- // would double-encode into `"\"...\""`. Fail loud instead of shipping bad JSON.
34
23
  if (typeof spec.data === "string") {
35
24
  throw new Error(
36
25
  'HttpRequest body kind:"json" expects a serializable object for "data", but received a string. ' +
@@ -72,8 +61,6 @@ export class HttpBodyBuilder {
72
61
  }
73
62
  }
74
63
  }
75
- // FormData sets its own Content-Type with boundary; empty string signals that
76
- // fetch should set it automatically.
77
64
  return {
78
65
  body: formData,
79
66
  contentType: "",
@@ -92,9 +79,6 @@ export class HttpBodyBuilder {
92
79
  if (!readResult) {
93
80
  throw new Error(`HttpRequest bodyFormat "binary": could not open read stream for slot "${spec.slot}".`);
94
81
  }
95
- // Pass the stream straight to fetch — no buffering. fetch's BodyInit
96
- // accepts a ReadableStream natively, so big attachments upload without
97
- // ever materialising the full payload in memory.
98
82
  return {
99
83
  body: readResult.body as unknown as NonNullable<RequestInit["body"]>,
100
84
  contentType: attachment.mimeType,
@@ -4,21 +4,6 @@ import type { HttpBodyBuilder } from "./HttpBodyBuilder";
4
4
  import type { HttpUrlBuilder } from "./HttpUrlBuilder";
5
5
  import type { SsrfGuard } from "./SsrfGuard";
6
6
 
7
- /**
8
- * Executes a single HTTP request described by {@link HttpRequestSpec}.
9
- *
10
- * - Credential sessions provide header/query deltas via `applyToRequest`.
11
- * - Body encoding is delegated to {@link HttpBodyBuilder}.
12
- * - URL query merging is delegated to {@link HttpUrlBuilder}.
13
- * - SSRF protection is delegated to {@link SsrfGuard} (injected).
14
- * - Binary response bodies: when `download.mode` triggers binary attach, the
15
- * `bodyBinaryName` field is set in the result but the body is NOT read here.
16
- * Callers that need binary attachment should use `buildRequest` to get the
17
- * resolved URL + init and make the fetch + binary attach themselves.
18
- *
19
- * Collaborators (`fetch`, body builder, url builder, ssrfGuard) are injected so
20
- * callers own construction at composition roots and tests can supply deterministic stubs.
21
- */
22
7
  export class HttpRequestExecutor {
23
8
  constructor(
24
9
  private readonly fetchFn: typeof globalThis.fetch,
@@ -27,14 +12,6 @@ export class HttpRequestExecutor {
27
12
  private readonly ssrfGuard: SsrfGuard,
28
13
  ) {}
29
14
 
30
- /**
31
- * Builds the fetch init (headers, query, body) from the spec + credential delta,
32
- * returning both the resolved URL and the RequestInit so callers can make the
33
- * actual fetch call themselves (useful for streaming / binary attach).
34
- *
35
- * Also performs SSRF protection via the injected {@link SsrfGuard} before
36
- * returning — throws {@link SSRFBlockedError} if the target is a private address.
37
- */
38
15
  async buildRequest(spec: HttpRequestSpec, item: Item): Promise<Readonly<{ url: string; init: RequestInit }>> {
39
16
  await this.ssrfGuard.check(spec.url, spec.allowPrivateNetworkTargets ?? false);
40
17
 
@@ -52,10 +29,6 @@ export class HttpRequestExecutor {
52
29
 
53
30
  const encodedBody = await this.bodyBuilder.build(spec.body, item, spec.ctx);
54
31
 
55
- // Only set Content-Type from the encoded body when it is non-empty
56
- // (empty string = FormData will set it automatically).
57
- // Explicit headers always win — only apply the body-derived content-type
58
- // when the caller has not already set one (case-insensitive check).
59
32
  const hasExplicitContentType = Object.keys(mergedHeaders).some((k) => k.toLowerCase() === "content-type");
60
33
  if (encodedBody && encodedBody.contentType && !hasExplicitContentType) {
61
34
  mergedHeaders["content-type"] = encodedBody.contentType;
@@ -72,12 +45,6 @@ export class HttpRequestExecutor {
72
45
  return { url: resolvedUrl, init };
73
46
  }
74
47
 
75
- /**
76
- * Executes an HTTP request and returns parsed result.
77
- * For binary downloads (when `shouldAttachBody` is true), the body is NOT consumed
78
- * and callers must call `ctx.binary.attach` directly using the resolved URL + init
79
- * (available via `buildRequest`).
80
- */
81
48
  async execute(spec: HttpRequestSpec, item: Item): Promise<HttpRequestResult> {
82
49
  const { url: resolvedUrl, init } = await this.buildRequest(spec, item);
83
50
 
@@ -97,9 +64,7 @@ export class HttpRequestExecutor {
97
64
  let bodyBinaryName: string | undefined;
98
65
 
99
66
  if (shouldDownload) {
100
- // Signal to caller that binary attachment is needed.
101
67
  bodyBinaryName = binaryName;
102
- // Do NOT read the body here — the caller must handle binary attach separately.
103
68
  } else if (isJson) {
104
69
  try {
105
70
  json = await response.json();
@@ -1,7 +1,3 @@
1
- /**
2
- * Merges query parameters into a base URL.
3
- * Handles both scalar and array values, and preserves any existing params.
4
- */
5
1
  export class HttpUrlBuilder {
6
2
  build(baseUrl: string, query?: Readonly<Record<string, string | string[]>>): string {
7
3
  if (!query || Object.keys(query).length === 0) {
@@ -1,7 +1,3 @@
1
- /**
2
- * Thrown when an HTTP request target resolves to a private, link-local, or
3
- * loopback address and `allowPrivateNetworkTargets` is not set.
4
- */
5
1
  export class SSRFBlockedError extends Error {
6
2
  readonly resolvedIp: string;
7
3