@codemation/core-nodes 0.12.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.
- package/CHANGELOG.md +30 -0
- package/dist/index.cjs +180 -413
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -1303
- package/dist/index.d.ts +99 -1303
- package/dist/index.js +181 -415
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +1 -1
- package/package.json +2 -2
- package/src/authoring/defineRestNode.types.ts +0 -84
- package/src/canvasIconName.ts +0 -7
- package/src/chatModels/CodemationChatModelConfig.ts +0 -10
- package/src/chatModels/CodemationChatModelFactory.ts +0 -7
- package/src/chatModels/ManagedHmacSignerFactory.types.ts +0 -35
- package/src/chatModels/OpenAIChatModelFactory.ts +0 -2
- package/src/chatModels/OpenAiChatModelPresetsFactory.ts +0 -5
- package/src/chatModels/OpenAiCredentialSession.ts +0 -1
- package/src/chatModels/OpenAiStrictJsonSchemaFactory.ts +0 -21
- package/src/credentials/ApiKeyCredentialType.ts +0 -3
- package/src/credentials/BasicAuthCredentialType.ts +0 -4
- package/src/credentials/BearerTokenCredentialType.ts +0 -4
- package/src/credentials/OAuth2ClientCredentialsTypeFactory.ts +0 -19
- package/src/credentials/OAuth2TokenExchangeFactory.ts +0 -7
- package/src/http/HttpBodyBuilder.ts +0 -16
- package/src/http/HttpRequestExecutor.ts +0 -35
- package/src/http/HttpUrlBuilder.ts +0 -4
- package/src/http/SSRFBlockedError.ts +0 -4
- package/src/http/SsrfGuard.ts +10 -50
- package/src/http/httpRequest.types.ts +0 -49
- package/src/index.ts +1 -0
- package/src/nodes/AIAgentConfig.ts +3 -39
- package/src/nodes/AIAgentExecutionHelpersFactory.ts +0 -37
- package/src/nodes/AIAgentNode.ts +4 -134
- package/src/nodes/AgentBinaryContentFactory.ts +0 -12
- package/src/nodes/AgentLoopCheckpoint.types.ts +0 -13
- package/src/nodes/AgentMessageFactory.ts +17 -19
- package/src/nodes/AgentStructuredOutputRunner.ts +0 -17
- package/src/nodes/AgentToolExecutionCoordinator.ts +0 -12
- package/src/nodes/AgentToolResultContentFactory.ts +126 -0
- package/src/nodes/AssertionNode.ts +0 -14
- package/src/nodes/BM25Index.ts +0 -14
- package/src/nodes/ConnectionCredentialExecutionContextFactory.ts +0 -5
- package/src/nodes/ConnectionCredentialNode.ts +0 -4
- package/src/nodes/CronTriggerFactory.ts +0 -9
- package/src/nodes/DeferredMetaToolStrategy.ts +0 -18
- package/src/nodes/DeferredMetaToolStrategyFactory.ts +0 -5
- package/src/nodes/HttpRequestNodeFactory.ts +0 -14
- package/src/nodes/InboxApprovalNode.types.ts +0 -16
- package/src/nodes/IsTestRunNode.ts +0 -8
- package/src/nodes/ManualTriggerFactory.ts +0 -3
- package/src/nodes/ManualTriggerNode.ts +0 -4
- package/src/nodes/MergeNode.ts +0 -1
- package/src/nodes/NodeBackedToolRuntime.ts +0 -14
- package/src/nodes/SubWorkflowNode.ts +0 -3
- package/src/nodes/SwitchNode.ts +0 -3
- package/src/nodes/TestTriggerNode.ts +0 -9
- package/src/nodes/aiAgentSupport.types.ts +0 -16
- package/src/nodes/assertion.ts +0 -10
- package/src/nodes/codemationDocumentScannerNode.ts +0 -18
- package/src/nodes/collections/collectionListNode.types.ts +0 -1
- package/src/nodes/httpRequest.ts +0 -68
- package/src/nodes/isTestRun.ts +0 -4
- package/src/nodes/mapData.ts +0 -1
- package/src/nodes/merge.ts +0 -4
- package/src/nodes/mergeExecutionUtils.types.ts +0 -3
- package/src/nodes/nodeOptions.types.ts +0 -8
- package/src/nodes/schedulePollingTrigger.ts +37 -0
- package/src/nodes/split.ts +0 -4
- package/src/nodes/testTrigger.ts +0 -21
- package/src/nodes/wait.ts +0 -1
- package/src/nodes/webhookTriggerNode.ts +0 -5
- package/src/register.types.ts +0 -10
- package/src/workflows/AIAgentConnectionWorkflowExpander.ts +0 -3
package/dist/metadata.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemation/core-nodes",
|
|
3
|
-
"version": "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.
|
|
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>;
|
package/src/canvasIconName.ts
CHANGED
|
@@ -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
|
|
|
@@ -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) {
|