@codemation/core-nodes 0.4.2 → 1.0.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/dist/index.cjs CHANGED
@@ -23,18 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let __codemation_core = require("@codemation/core");
25
25
  __codemation_core = __toESM(__codemation_core);
26
- let __langchain_openai = require("@langchain/openai");
27
- __langchain_openai = __toESM(__langchain_openai);
28
- let __langchain_core_messages = require("@langchain/core/messages");
29
- __langchain_core_messages = __toESM(__langchain_core_messages);
30
- let __langchain_core_utils_types = require("@langchain/core/utils/types");
31
- __langchain_core_utils_types = __toESM(__langchain_core_utils_types);
32
- let __langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
33
- __langchain_core_utils_json_schema = __toESM(__langchain_core_utils_json_schema);
34
- let __langchain_core_tools = require("@langchain/core/tools");
35
- __langchain_core_tools = __toESM(__langchain_core_tools);
26
+ let __ai_sdk_openai = require("@ai-sdk/openai");
27
+ __ai_sdk_openai = __toESM(__ai_sdk_openai);
36
28
  let __codemation_core_bootstrap = require("@codemation/core/bootstrap");
37
29
  __codemation_core_bootstrap = __toESM(__codemation_core_bootstrap);
30
+ let ai = require("ai");
31
+ ai = __toESM(ai);
38
32
 
39
33
  //#region \0@oxc-project+runtime@0.95.0/helpers/decorate.js
40
34
  function __decorate(decorators, target, key, desc) {
@@ -49,166 +43,21 @@ function __decorate(decorators, target, key, desc) {
49
43
  let OpenAIChatModelFactory = class OpenAIChatModelFactory$1 {
50
44
  async create(args) {
51
45
  const session = await args.ctx.getCredential(args.config.credentialSlotKey);
52
- return new __langchain_openai.ChatOpenAI({
53
- apiKey: session.apiKey,
54
- model: args.config.model,
55
- temperature: args.config.options?.temperature,
56
- maxTokens: args.config.options?.maxTokens,
57
- configuration: session.baseUrl ? { baseURL: session.baseUrl } : void 0
58
- });
59
- }
60
- };
61
- OpenAIChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], OpenAIChatModelFactory);
62
-
63
- //#endregion
64
- //#region src/chatModels/OpenAIStructuredOutputMethodFactory.ts
65
- var _OpenAIStructuredOutputMethodFactory;
66
- let OpenAIStructuredOutputMethodFactory = class OpenAIStructuredOutputMethodFactory$1 {
67
- static {
68
- _OpenAIStructuredOutputMethodFactory = this;
69
- }
70
- static isoDatePattern = /^\d{4}-\d{2}-\d{2}$/;
71
- create(chatModelConfig) {
72
- if (chatModelConfig.type !== OpenAIChatModelFactory) return;
73
- const model = this.readModelName(chatModelConfig);
74
- if (!model) return {
75
- method: "functionCalling",
76
- strict: true
77
- };
78
46
  return {
79
- method: this.supportsJsonSchema(model) ? "jsonSchema" : "functionCalling",
80
- strict: true
81
- };
82
- }
83
- readModelName(chatModelConfig) {
84
- const candidate = chatModelConfig;
85
- return typeof candidate.model === "string" ? candidate.model : void 0;
86
- }
87
- supportsJsonSchema(model) {
88
- if (model === "gpt-4o" || model === "gpt-4o-mini") return true;
89
- return this.supportsSnapshotAtOrAfter(model, "gpt-4o-", "2024-08-06") || this.supportsSnapshotAtOrAfter(model, "gpt-4o-mini-", "2024-07-18");
90
- }
91
- supportsSnapshotAtOrAfter(model, prefix, minimumSnapshotDate) {
92
- if (!model.startsWith(prefix)) return false;
93
- const snapshotDate = model.slice(prefix.length);
94
- return _OpenAIStructuredOutputMethodFactory.isoDatePattern.test(snapshotDate) && snapshotDate >= minimumSnapshotDate;
95
- }
96
- };
97
- OpenAIStructuredOutputMethodFactory = _OpenAIStructuredOutputMethodFactory = __decorate([(0, __codemation_core.injectable)()], OpenAIStructuredOutputMethodFactory);
98
-
99
- //#endregion
100
- //#region src/chatModels/openAiChatModelConfig.ts
101
- var OpenAIChatModelConfig = class {
102
- type = OpenAIChatModelFactory;
103
- presentation;
104
- provider = "openai";
105
- modelName;
106
- constructor(name, model, credentialSlotKey = "openai", presentationIn, options) {
107
- this.name = name;
108
- this.model = model;
109
- this.credentialSlotKey = credentialSlotKey;
110
- this.options = options;
111
- this.modelName = model;
112
- this.presentation = presentationIn ?? {
113
- icon: "builtin:openai",
114
- label: name
115
- };
116
- }
117
- getCredentialRequirements() {
118
- return [{
119
- slotKey: this.credentialSlotKey,
120
- label: "OpenAI API key",
121
- acceptedTypes: ["openai.apiKey"]
122
- }];
123
- }
124
- };
125
-
126
- //#endregion
127
- //#region src/chatModels/OpenAiChatModelPresetsFactory.ts
128
- /**
129
- * Default OpenAI chat model configs for scaffolds and demos (icon + label match {@link OpenAIChatModelConfig} defaults).
130
- * Prefer importing {@link openAiChatModelPresets} from here or from the consumer template re-export
131
- * instead of repeating {@link OpenAIChatModelConfig} construction in app workflows.
132
- */
133
- var OpenAiChatModelPresets = class {
134
- demoGpt4oMini = new OpenAIChatModelConfig("OpenAI", "gpt-4o-mini");
135
- demoGpt41 = new OpenAIChatModelConfig("OpenAI", "gpt-4.1");
136
- };
137
- const openAiChatModelPresets = new OpenAiChatModelPresets();
138
-
139
- //#endregion
140
- //#region src/nodes/AgentMessageFactory.ts
141
- var AgentMessageFactory = class {
142
- static createPromptMessages(messages) {
143
- return messages.map((message) => this.createPromptMessage(message));
144
- }
145
- static createSystemPrompt(systemMessage) {
146
- return new __langchain_core_messages.SystemMessage(systemMessage);
147
- }
148
- static createUserPrompt(prompt) {
149
- return new __langchain_core_messages.HumanMessage(prompt);
150
- }
151
- static createAssistantPrompt(prompt) {
152
- return new __langchain_core_messages.AIMessage(prompt);
153
- }
154
- static createToolMessage(toolCallId, content) {
155
- return new __langchain_core_messages.ToolMessage({
156
- tool_call_id: toolCallId,
157
- content
158
- });
159
- }
160
- static extractContent(message) {
161
- if (typeof message === "string") return message;
162
- if (!this.isRecord(message)) return String(message);
163
- const content = message.content;
164
- if (typeof content === "string") return content;
165
- if (Array.isArray(content)) return content.map((part) => {
166
- if (typeof part === "string") return part;
167
- if (this.isRecord(part) && typeof part.text === "string") return part.text;
168
- return JSON.stringify(part);
169
- }).join("\n");
170
- return JSON.stringify(content);
171
- }
172
- static extractToolCalls(message) {
173
- if (!this.isRecord(message)) return [];
174
- const toolCalls = message.tool_calls;
175
- if (!Array.isArray(toolCalls)) return [];
176
- return toolCalls.filter((toolCall) => this.isRecord(toolCall) && typeof toolCall.name === "string").map((toolCall) => ({
177
- id: typeof toolCall.id === "string" ? toolCall.id : void 0,
178
- name: toolCall.name,
179
- input: this.isRecord(toolCall) && "args" in toolCall ? toolCall.args : void 0
180
- }));
181
- }
182
- static isRecord(value) {
183
- return typeof value === "object" && value !== null;
184
- }
185
- static createPromptMessage(message) {
186
- if (message.role === "system") return this.createSystemPrompt(message.content);
187
- if (message.role === "assistant") return this.createAssistantPrompt(message.content);
188
- return this.createUserPrompt(message.content);
189
- }
190
- };
191
-
192
- //#endregion
193
- //#region src/nodes/AgentOutputFactory.ts
194
- var AgentOutputFactory = class {
195
- static fromUnknown(value) {
196
- return { main: [{ json: value }] };
197
- }
198
- static replaceJson(item, value) {
199
- return {
200
- ...item,
201
- json: value
47
+ languageModel: (0, __ai_sdk_openai.createOpenAI)({
48
+ apiKey: session.apiKey,
49
+ baseURL: session.baseUrl
50
+ }).chat(args.config.model),
51
+ modelName: args.config.model,
52
+ provider: "openai",
53
+ defaultCallOptions: {
54
+ maxOutputTokens: args.config.options?.maxTokens,
55
+ temperature: args.config.options?.temperature
56
+ }
202
57
  };
203
58
  }
204
- static fromAgentContent(content) {
205
- try {
206
- return JSON.parse(content);
207
- } catch {
208
- return { output: content };
209
- }
210
- }
211
59
  };
60
+ OpenAIChatModelFactory = __decorate([(0, __codemation_core.chatModel)({ packageName: "@codemation/core-nodes" })], OpenAIChatModelFactory);
212
61
 
213
62
  //#endregion
214
63
  //#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/core.js
@@ -2130,53 +1979,35 @@ let AIAgentExecutionHelpersFactory = class AIAgentExecutionHelpersFactory$1 {
2130
1979
  createConnectionCredentialExecutionContextFactory(credentialSessions) {
2131
1980
  return new ConnectionCredentialExecutionContextFactory(credentialSessions);
2132
1981
  }
2133
- createDynamicStructuredTool(entry, toolCredentialContext, item, itemIndex, items) {
2134
- if (entry.runtime.inputSchema == null) throw new Error(`Cannot create LangChain tool "${entry.config.name}": missing inputSchema (broken tool runtime resolution).`);
2135
- const schemaForOpenAi = this.createJsonSchemaRecord(entry.runtime.inputSchema, {
2136
- schemaName: entry.config.name,
2137
- requireObjectRoot: true
2138
- });
2139
- return new __langchain_core_tools.DynamicStructuredTool({
2140
- name: entry.config.name,
2141
- description: entry.config.description ?? entry.runtime.defaultDescription,
2142
- schema: schemaForOpenAi,
2143
- func: async (input) => {
2144
- const result = await entry.runtime.execute({
2145
- config: entry.config,
2146
- input,
2147
- ctx: toolCredentialContext,
2148
- item,
2149
- itemIndex,
2150
- items
2151
- });
2152
- return JSON.stringify(result);
2153
- }
2154
- });
2155
- }
2156
1982
  /**
2157
- * Produces a plain JSON Schema object for OpenAI tool parameters and LangChain tool invocation:
2158
- * - **Zod** `toJSONSchema(..., { target: "draft-07" })` so shapes match what `@cfworker/json-schema`
2159
- * expects (`required` must be an array; draft 2020-12 output can break validation).
2160
- * - Otherwise LangChain `toJsonSchema` (Standard Schema + JSON passthrough); if the result is still Zod
2161
- * (duplicate `zod` copies), fall back to Zod `toJSONSchema` with draft-07.
2162
- * - Strip root `$schema` for OpenAI; normalize invalid `required` keywords for cfworker; ensure `properties`.
1983
+ * Produces a plain JSON Schema object (`draft-07`) from a Zod schema, as needed by
1984
+ * OpenAI tool-parameter schemas and the structured-output repair prompt.
1985
+ * - Prefers the schema's **instance** `toJSONSchema(...)` method so we stay inside the Zod
1986
+ * instance that created the schema (works across consumer/framework tsx namespaces see
1987
+ * {@link ZodInstanceToJsonSchema}). Falls back to the framework-imported module function.
1988
+ * - Strips root `$schema` (OpenAI ignores it).
1989
+ * - Sanitizes `required` for cfworker json-schema compatibility (must be a string array or absent).
2163
1990
  */
2164
1991
  createJsonSchemaRecord(inputSchema, options) {
2165
- const draft07Params = { target: "draft-07" };
2166
- let converted;
2167
- if ((0, __langchain_core_utils_types.isInteropZodSchema)(inputSchema)) converted = toJSONSchema(inputSchema, draft07Params);
2168
- else {
2169
- converted = (0, __langchain_core_utils_json_schema.toJsonSchema)(inputSchema);
2170
- if ((0, __langchain_core_utils_types.isInteropZodSchema)(converted)) converted = toJSONSchema(inputSchema, draft07Params);
2171
- }
2172
- const { $schema: _draftSchemaOmitted,...rest } = converted;
2173
- if (options.requireObjectRoot && rest.type !== "object") throw new Error(`Cannot create LangChain tool "${options.schemaName}": tool input schema must be a JSON Schema object type (got type=${String(rest.type)}).`);
2174
- if (options.requireObjectRoot && rest.properties !== void 0 && (typeof rest.properties !== "object" || Array.isArray(rest.properties))) throw new Error(`Cannot create LangChain tool "${options.schemaName}": tool input schema "properties" must be an object (got ${JSON.stringify(rest.properties)}).`);
1992
+ const { $schema: _draftSchemaOmitted,...rest } = this.convertZodSchemaToJsonSchema(inputSchema, { target: "draft-07" });
1993
+ if (options.requireObjectRoot && rest.type !== "object") throw new Error(`Cannot create tool "${options.schemaName}": tool input schema must be a JSON Schema object type (got type=${String(rest.type)}).`);
1994
+ if (options.requireObjectRoot && rest.properties !== void 0 && (typeof rest.properties !== "object" || Array.isArray(rest.properties))) throw new Error(`Cannot create tool "${options.schemaName}": tool input schema "properties" must be an object (got ${JSON.stringify(rest.properties)}).`);
2175
1995
  if (options.requireObjectRoot && rest.properties === void 0) rest.properties = {};
2176
1996
  this.sanitizeJsonSchemaRequiredKeywordsForCfworker(rest);
2177
1997
  return rest;
2178
1998
  }
2179
1999
  /**
2000
+ * Runs Zod's `toJSONSchema` via the schema's own instance method when available, so consumer
2001
+ * schemas loaded under a different tsx namespace still convert correctly. If the caller handed us
2002
+ * a payload that lacks that method (e.g. a plain JSON Schema record or a Zod instance whose
2003
+ * prototype was stripped), we fall back to the framework-bundled module function.
2004
+ */
2005
+ convertZodSchemaToJsonSchema(inputSchema, params) {
2006
+ const candidate = inputSchema.toJSONSchema;
2007
+ if (typeof candidate === "function") return candidate.call(inputSchema, params);
2008
+ return toJSONSchema(inputSchema, params);
2009
+ }
2010
+ /**
2180
2011
  * `@cfworker/json-schema` iterates `schema.required` with `for...of`; it must be a string array or absent.
2181
2012
  */
2182
2013
  sanitizeJsonSchemaRequiredKeywordsForCfworker(node$16) {
@@ -2223,6 +2054,214 @@ function __decorateParam(paramIndex, decorator) {
2223
2054
  };
2224
2055
  }
2225
2056
 
2057
+ //#endregion
2058
+ //#region src/chatModels/OpenAiStrictJsonSchemaFactory.ts
2059
+ var _ref$5;
2060
+ let OpenAiStrictJsonSchemaFactory = class OpenAiStrictJsonSchemaFactory$1 {
2061
+ constructor(executionHelpers) {
2062
+ this.executionHelpers = executionHelpers;
2063
+ }
2064
+ createStructuredOutputRecord(schema, options) {
2065
+ const record = this.executionHelpers.createJsonSchemaRecord(schema, {
2066
+ schemaName: options.schemaName,
2067
+ requireObjectRoot: false
2068
+ });
2069
+ this.strictifyRecursive(record);
2070
+ if (options.title !== void 0) record.title = options.title;
2071
+ return record;
2072
+ }
2073
+ strictifyRecursive(node$16) {
2074
+ if (!node$16 || typeof node$16 !== "object" || Array.isArray(node$16)) return;
2075
+ const o = node$16;
2076
+ this.stripOpenAiRejectedKeywords(o);
2077
+ if (this.isObjectNode(o)) {
2078
+ const props = this.readPropertiesObject(o);
2079
+ o.properties = props;
2080
+ o.additionalProperties = false;
2081
+ o.required = Object.keys(props);
2082
+ for (const value of Object.values(props)) this.strictifyRecursive(value);
2083
+ }
2084
+ this.recurseIntoComposites(o);
2085
+ }
2086
+ stripOpenAiRejectedKeywords(o) {
2087
+ delete o["$schema"];
2088
+ delete o["unevaluatedProperties"];
2089
+ delete o["default"];
2090
+ }
2091
+ isObjectNode(o) {
2092
+ const typeIsObject = o.type === "object" || Array.isArray(o.type) && o.type.includes("object");
2093
+ const hasObjectProperties = o.properties !== void 0 && typeof o.properties === "object" && !Array.isArray(o.properties);
2094
+ return typeIsObject || hasObjectProperties;
2095
+ }
2096
+ readPropertiesObject(o) {
2097
+ if (o.properties && typeof o.properties === "object" && !Array.isArray(o.properties)) return o.properties;
2098
+ return {};
2099
+ }
2100
+ recurseIntoComposites(o) {
2101
+ for (const key of [
2102
+ "allOf",
2103
+ "anyOf",
2104
+ "oneOf",
2105
+ "prefixItems"
2106
+ ]) {
2107
+ const branch = o[key];
2108
+ if (Array.isArray(branch)) for (const sub of branch) this.strictifyRecursive(sub);
2109
+ }
2110
+ if (o.not) this.strictifyRecursive(o.not);
2111
+ if (o.items) if (Array.isArray(o.items)) for (const sub of o.items) this.strictifyRecursive(sub);
2112
+ else this.strictifyRecursive(o.items);
2113
+ for (const key of [
2114
+ "if",
2115
+ "then",
2116
+ "else"
2117
+ ]) if (o[key]) this.strictifyRecursive(o[key]);
2118
+ for (const key of ["$defs", "definitions"]) {
2119
+ const defs = o[key];
2120
+ if (defs && typeof defs === "object" && !Array.isArray(defs)) for (const sub of Object.values(defs)) this.strictifyRecursive(sub);
2121
+ }
2122
+ }
2123
+ };
2124
+ OpenAiStrictJsonSchemaFactory = __decorate([
2125
+ (0, __codemation_core.injectable)(),
2126
+ __decorateParam(0, (0, __codemation_core.inject)(AIAgentExecutionHelpersFactory)),
2127
+ __decorateMetadata("design:paramtypes", [typeof (_ref$5 = typeof AIAgentExecutionHelpersFactory !== "undefined" && AIAgentExecutionHelpersFactory) === "function" ? _ref$5 : Object])
2128
+ ], OpenAiStrictJsonSchemaFactory);
2129
+
2130
+ //#endregion
2131
+ //#region src/chatModels/openAiChatModelConfig.ts
2132
+ var OpenAIChatModelConfig = class {
2133
+ type = OpenAIChatModelFactory;
2134
+ presentation;
2135
+ provider = "openai";
2136
+ modelName;
2137
+ constructor(name, model, credentialSlotKey = "openai", presentationIn, options) {
2138
+ this.name = name;
2139
+ this.model = model;
2140
+ this.credentialSlotKey = credentialSlotKey;
2141
+ this.options = options;
2142
+ this.modelName = model;
2143
+ this.presentation = presentationIn ?? {
2144
+ icon: "builtin:openai",
2145
+ label: name
2146
+ };
2147
+ }
2148
+ getCredentialRequirements() {
2149
+ return [{
2150
+ slotKey: this.credentialSlotKey,
2151
+ label: "OpenAI API key",
2152
+ acceptedTypes: ["openai.apiKey"]
2153
+ }];
2154
+ }
2155
+ };
2156
+
2157
+ //#endregion
2158
+ //#region src/chatModels/OpenAiChatModelPresetsFactory.ts
2159
+ /**
2160
+ * Default OpenAI chat model configs for scaffolds and demos (icon + label match {@link OpenAIChatModelConfig} defaults).
2161
+ * Prefer importing {@link openAiChatModelPresets} from here or from the consumer template re-export
2162
+ * instead of repeating {@link OpenAIChatModelConfig} construction in app workflows.
2163
+ */
2164
+ var OpenAiChatModelPresets = class {
2165
+ demoGpt4oMini = new OpenAIChatModelConfig("OpenAI", "gpt-4o-mini");
2166
+ demoGpt41 = new OpenAIChatModelConfig("OpenAI", "gpt-4.1");
2167
+ };
2168
+ const openAiChatModelPresets = new OpenAiChatModelPresets();
2169
+
2170
+ //#endregion
2171
+ //#region src/nodes/AgentMessageFactory.ts
2172
+ /**
2173
+ * AI-SDK-shaped message construction for the AIAgent stack. Emits plain `ModelMessage[]`
2174
+ * ( `{ role: 'system' | 'user' | 'assistant' | 'tool', content: ... }` ) as consumed by
2175
+ * `generateText({ messages })` from the `ai` package.
2176
+ */
2177
+ var AgentMessageFactory = class AgentMessageFactory {
2178
+ static createPromptMessages(messages) {
2179
+ return messages.map((message) => this.createPromptMessage(message));
2180
+ }
2181
+ /**
2182
+ * Builds the assistant message that contains optional text plus one or more tool-call parts,
2183
+ * matching the shape AI SDK emits between steps.
2184
+ */
2185
+ static createAssistantWithToolCalls(text, toolCalls) {
2186
+ const content = [];
2187
+ if (text && text.length > 0) content.push({
2188
+ type: "text",
2189
+ text
2190
+ });
2191
+ for (const toolCall of toolCalls) content.push({
2192
+ type: "tool-call",
2193
+ toolCallId: toolCall.id ?? toolCall.name,
2194
+ toolName: toolCall.name,
2195
+ input: toolCall.input ?? {}
2196
+ });
2197
+ return {
2198
+ role: "assistant",
2199
+ content
2200
+ };
2201
+ }
2202
+ /**
2203
+ * Builds the `{ role: "tool", content: [{ type: "tool-result", ... }, ...] }` message returned
2204
+ * to the model after each tool round.
2205
+ */
2206
+ static createToolResultsMessage(executedToolCalls) {
2207
+ return {
2208
+ role: "tool",
2209
+ content: executedToolCalls.map((executed) => ({
2210
+ type: "tool-result",
2211
+ toolCallId: executed.toolCallId,
2212
+ toolName: executed.toolName,
2213
+ output: {
2214
+ type: "json",
2215
+ value: AgentMessageFactory.toToolResultJson(executed.result)
2216
+ }
2217
+ }))
2218
+ };
2219
+ }
2220
+ static toToolResultJson(value) {
2221
+ if (value === void 0) return null;
2222
+ try {
2223
+ return JSON.parse(JSON.stringify(value));
2224
+ } catch {
2225
+ return String(value);
2226
+ }
2227
+ }
2228
+ static createPromptMessage(message) {
2229
+ if (message.role === "system") return {
2230
+ role: "system",
2231
+ content: message.content
2232
+ };
2233
+ if (message.role === "assistant") return {
2234
+ role: "assistant",
2235
+ content: message.content
2236
+ };
2237
+ return {
2238
+ role: "user",
2239
+ content: message.content
2240
+ };
2241
+ }
2242
+ };
2243
+
2244
+ //#endregion
2245
+ //#region src/nodes/AgentOutputFactory.ts
2246
+ var AgentOutputFactory = class {
2247
+ static fromUnknown(value) {
2248
+ return { main: [{ json: value }] };
2249
+ }
2250
+ static replaceJson(item, value) {
2251
+ return {
2252
+ ...item,
2253
+ json: value
2254
+ };
2255
+ }
2256
+ static fromAgentContent(content) {
2257
+ try {
2258
+ return JSON.parse(content);
2259
+ } catch {
2260
+ return { output: content };
2261
+ }
2262
+ }
2263
+ };
2264
+
2226
2265
  //#endregion
2227
2266
  //#region src/nodes/AgentStructuredOutputRepairPromptFactory.ts
2228
2267
  var _ref$4, _AgentStructuredOutputRepairPromptFactory;
@@ -2601,31 +2640,26 @@ let AgentStructuredOutputRunner = class AgentStructuredOutputRunner$1 {
2601
2640
  _AgentStructuredOutputRunner = this;
2602
2641
  }
2603
2642
  static repairAttemptCount = 2;
2604
- constructor(repairPromptFactory, openAiStructuredOutputMethodFactory) {
2643
+ static structuredOutputSchemaName = "agent_output";
2644
+ constructor(repairPromptFactory, openAiStrictJsonSchemaFactory) {
2605
2645
  this.repairPromptFactory = repairPromptFactory;
2606
- this.openAiStructuredOutputMethodFactory = openAiStructuredOutputMethodFactory;
2646
+ this.openAiStrictJsonSchemaFactory = openAiStrictJsonSchemaFactory;
2607
2647
  }
2608
2648
  async resolve(args) {
2609
2649
  let lastFailure;
2610
- if (args.rawFinalResponse) {
2611
- const directResult = this.tryParseAndValidate(AgentMessageFactory.extractContent(args.rawFinalResponse), args.schema);
2612
- if (directResult.ok) return directResult.value;
2613
- lastFailure = directResult;
2614
- } else if (!this.supportsNativeStructuredOutput(args.model)) {
2615
- const rawResponse = await args.invokeTextModel(args.conversation);
2616
- const directResult = this.tryParseAndValidate(AgentMessageFactory.extractContent(rawResponse), args.schema);
2650
+ if (args.rawFinalText !== void 0) {
2651
+ const directResult = this.tryParseAndValidate(args.rawFinalText, args.schema);
2617
2652
  if (directResult.ok) return directResult.value;
2618
2653
  lastFailure = directResult;
2619
2654
  }
2620
2655
  try {
2621
- const nativeStructuredModel = this.createStructuredOutputModel(args.model, args.chatModelConfig, args.schema);
2622
- if (nativeStructuredModel) {
2623
- const nativeResult = this.tryValidateStructuredValue(await args.invokeStructuredModel(nativeStructuredModel, args.conversation), args.schema);
2624
- if (nativeResult.ok) return nativeResult.value;
2625
- lastFailure = nativeResult;
2626
- }
2656
+ const structuredOptions = this.resolveStructuredOutputOptions(args.chatModelConfig);
2657
+ const schemaForModel = this.resolveOutputSchemaForModel(args.schema, structuredOptions);
2658
+ const nativeResult = this.tryValidateStructuredValue(await args.invokeStructuredModel(schemaForModel, args.conversation, structuredOptions), args.schema);
2659
+ if (nativeResult.ok) return nativeResult.value;
2660
+ lastFailure = nativeResult;
2627
2661
  } catch (error) {
2628
- lastFailure = {
2662
+ lastFailure = lastFailure ?? {
2629
2663
  ok: false,
2630
2664
  invalidContent: "",
2631
2665
  validationError: `Native structured output failed: ${this.summarizeError(error)}`
@@ -2649,22 +2683,26 @@ let AgentStructuredOutputRunner = class AgentStructuredOutputRunner$1 {
2649
2683
  validationError: failure.validationError
2650
2684
  }))];
2651
2685
  const repairResponse = await args.invokeTextModel(repairMessages);
2652
- const repairResult = this.tryParseAndValidate(AgentMessageFactory.extractContent(repairResponse), args.schema);
2686
+ const repairResult = this.tryParseAndValidate(repairResponse.text, args.schema);
2653
2687
  if (repairResult.ok) return repairResult.value;
2654
2688
  failure = repairResult;
2655
2689
  }
2656
2690
  throw new Error(`Structured output required for AIAgent "${args.agentName}" (${args.nodeId}) but validation still failed after ${_AgentStructuredOutputRunner.repairAttemptCount} repair attempts: ${failure.validationError}`);
2657
2691
  }
2658
- createStructuredOutputModel(model, chatModelConfig, schema) {
2659
- if (!this.supportsNativeStructuredOutput(model)) return;
2660
- const options = this.getStructuredOutputOptions(chatModelConfig);
2661
- return model.withStructuredOutput(schema, options);
2662
- }
2663
- getStructuredOutputOptions(chatModelConfig) {
2664
- return this.openAiStructuredOutputMethodFactory.create(chatModelConfig) ?? { strict: true };
2692
+ /**
2693
+ * Chooses strict mode for OpenAI chat-model configs, off otherwise. Extendable in future for
2694
+ * other providers that adopt the same "supply a JSON Schema record directly" contract.
2695
+ */
2696
+ resolveStructuredOutputOptions(chatModelConfig) {
2697
+ if (chatModelConfig.type !== OpenAIChatModelFactory) return;
2698
+ return {
2699
+ strict: true,
2700
+ schemaName: _AgentStructuredOutputRunner.structuredOutputSchemaName
2701
+ };
2665
2702
  }
2666
- supportsNativeStructuredOutput(model) {
2667
- return typeof model.withStructuredOutput === "function";
2703
+ resolveOutputSchemaForModel(schema, options) {
2704
+ if (!options?.strict) return schema;
2705
+ return this.openAiStrictJsonSchemaFactory.createStructuredOutputRecord(schema, { schemaName: options.schemaName ?? _AgentStructuredOutputRunner.structuredOutputSchemaName });
2668
2706
  }
2669
2707
  tryParseAndValidate(content, schema) {
2670
2708
  try {
@@ -2698,7 +2736,7 @@ let AgentStructuredOutputRunner = class AgentStructuredOutputRunner$1 {
2698
2736
  }
2699
2737
  toJson(value) {
2700
2738
  try {
2701
- return JSON.stringify(value);
2739
+ return JSON.stringify(value) ?? String(value);
2702
2740
  } catch (error) {
2703
2741
  return `<<unserializable: ${this.summarizeError(error)}>>`;
2704
2742
  }
@@ -2707,8 +2745,8 @@ let AgentStructuredOutputRunner = class AgentStructuredOutputRunner$1 {
2707
2745
  AgentStructuredOutputRunner = _AgentStructuredOutputRunner = __decorate([
2708
2746
  (0, __codemation_core.injectable)(),
2709
2747
  __decorateParam(0, (0, __codemation_core.inject)(AgentStructuredOutputRepairPromptFactory)),
2710
- __decorateParam(1, (0, __codemation_core.inject)(OpenAIStructuredOutputMethodFactory)),
2711
- __decorateMetadata("design:paramtypes", [typeof (_ref$3 = typeof AgentStructuredOutputRepairPromptFactory !== "undefined" && AgentStructuredOutputRepairPromptFactory) === "function" ? _ref$3 : Object, typeof (_ref2$3 = typeof OpenAIStructuredOutputMethodFactory !== "undefined" && OpenAIStructuredOutputMethodFactory) === "function" ? _ref2$3 : Object])
2748
+ __decorateParam(1, (0, __codemation_core.inject)(OpenAiStrictJsonSchemaFactory)),
2749
+ __decorateMetadata("design:paramtypes", [typeof (_ref$3 = typeof AgentStructuredOutputRepairPromptFactory !== "undefined" && AgentStructuredOutputRepairPromptFactory) === "function" ? _ref$3 : Object, typeof (_ref2$3 = typeof OpenAiStrictJsonSchemaFactory !== "undefined" && OpenAiStrictJsonSchemaFactory) === "function" ? _ref2$3 : Object])
2712
2750
  ], AgentStructuredOutputRunner);
2713
2751
 
2714
2752
  //#endregion
@@ -2862,8 +2900,8 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2862
2900
  inputsByPort: toolCallInputsByPort
2863
2901
  });
2864
2902
  try {
2865
- const serialized = await plannedToolCall.binding.langChainTool.invoke(plannedToolCall.toolCall.input ?? {});
2866
- const result = this.parseToolOutput(serialized);
2903
+ const result = await plannedToolCall.binding.execute(plannedToolCall.toolCall.input ?? {});
2904
+ const serialized = typeof result === "string" ? result : JSON.stringify(result);
2867
2905
  const finishedAt = /* @__PURE__ */ new Date();
2868
2906
  await ctx.nodeState?.markCompleted({
2869
2907
  nodeId: plannedToolCall.nodeId,
@@ -2907,7 +2945,7 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2907
2945
  const classification = this.errorClassifier.classify({
2908
2946
  error,
2909
2947
  toolName: plannedToolCall.binding.config.name,
2910
- schema: plannedToolCall.binding.langChainTool.schema
2948
+ schema: plannedToolCall.binding.inputSchema
2911
2949
  });
2912
2950
  if (classification.kind !== "repairable_validation_error") {
2913
2951
  const effectiveError = classification.effectiveError;
@@ -3070,14 +3108,6 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
3070
3108
  if (!firstIssue) return `Your previous tool call for "${toolName}" was invalid and did not match the expected schema.`;
3071
3109
  return `Your previous tool call for "${toolName}" was invalid because field "${firstIssue.path.length > 0 ? firstIssue.path.join(".") : "<root>"}" failed validation: ${firstIssue.message}`;
3072
3110
  }
3073
- parseToolOutput(serialized) {
3074
- if (typeof serialized !== "string") return serialized;
3075
- try {
3076
- return JSON.parse(serialized);
3077
- } catch {
3078
- return serialized;
3079
- }
3080
- }
3081
3111
  toJsonValue(value) {
3082
3112
  if (value === void 0) return;
3083
3113
  return JSON.parse(JSON.stringify(value));
@@ -3197,14 +3227,8 @@ var _ref, _ref2, _ref3, _ref4;
3197
3227
  let AIAgentNode = class AIAgentNode$1 {
3198
3228
  kind = "node";
3199
3229
  outputPorts = ["main"];
3200
- /**
3201
- * Engine validates {@link RunnableNodeConfig.inputSchema} (Zod) on {@code item.json} before enqueue, then resolves
3202
- * per-item **`itemExpr`** leaves on config before {@link #execute}. Prefer modeling prompts as
3203
- * {@code { messages: [{ role, content }, ...] }} (on input or config) so persisted inputs are visible in the UI.
3204
- */
3205
3230
  inputSchema = unknown();
3206
3231
  connectionCredentialExecutionContextFactory;
3207
- /** One resolved model/tools bundle per activation context (same ctx across items in a batch). */
3208
3232
  preparedByExecutionContext = /* @__PURE__ */ new WeakMap();
3209
3233
  constructor(nodeResolver, credentialSessions, nodeBackedToolRuntime, executionHelpers, structuredOutputRunner, toolExecutionCoordinator) {
3210
3234
  this.nodeResolver = nodeResolver;
@@ -3235,9 +3259,6 @@ let AIAgentNode = class AIAgentNode$1 {
3235
3259
  throw error;
3236
3260
  }
3237
3261
  }
3238
- /**
3239
- * Resolves the chat model and tools once per activation, then reuses for every item in the batch.
3240
- */
3241
3262
  async prepareExecution(ctx) {
3242
3263
  const chatModelFactory = this.nodeResolver.resolve(ctx.config.chatModel.type);
3243
3264
  const languageModelCredentialContext = this.connectionCredentialExecutionContextFactory.forConnectionNode(ctx, {
@@ -3255,9 +3276,6 @@ let AIAgentNode = class AIAgentNode$1 {
3255
3276
  languageModelConnectionNodeId: __codemation_core.ConnectionNodeIdFactory.languageModelConnectionNodeId(ctx.nodeId)
3256
3277
  };
3257
3278
  }
3258
- /**
3259
- * One item: build prompts, optionally bind tools, run the multi-turn loop, map the final model message to workflow JSON.
3260
- */
3261
3279
  async runAgentForItem(prepared, item, itemIndex, items) {
3262
3280
  const { ctx } = prepared;
3263
3281
  const itemInputsByPort = AgentItemPortMap.fromItem(item);
@@ -3271,8 +3289,8 @@ let AIAgentNode = class AIAgentNode$1 {
3271
3289
  conversation,
3272
3290
  agentName: this.getAgentDisplayName(ctx),
3273
3291
  nodeId: ctx.nodeId,
3274
- invokeTextModel: async (messages) => await this.invokeModel(prepared.model, prepared.languageModelConnectionNodeId, messages, ctx, itemInputsByPort, prepared.guardrails.modelInvocationOptions),
3275
- invokeStructuredModel: async (structuredModel, messages) => await this.invokeStructuredModel(structuredModel, prepared.languageModelConnectionNodeId, messages, ctx, itemInputsByPort, prepared.guardrails.modelInvocationOptions)
3292
+ invokeTextModel: async (messages) => await this.invokeTextTurn(prepared, itemInputsByPort, messages, []),
3293
+ invokeStructuredModel: async (schema, messages, structuredOptions) => await this.invokeStructuredTurn(prepared, itemInputsByPort, schema, messages, structuredOptions)
3276
3294
  });
3277
3295
  await ctx.telemetry.recordMetric({
3278
3296
  name: __codemation_core.CodemationTelemetryMetricNames.agentTurns,
@@ -3284,13 +3302,11 @@ let AIAgentNode = class AIAgentNode$1 {
3284
3302
  });
3285
3303
  return this.buildOutputItem(item, structuredOutput);
3286
3304
  }
3287
- const modelWithTools = this.bindToolsToModel(prepared.model, itemScopedTools);
3288
3305
  const loopResult = await this.runTurnLoopUntilFinalAnswer({
3289
3306
  prepared,
3290
3307
  itemInputsByPort,
3291
3308
  itemScopedTools,
3292
- conversation,
3293
- modelWithTools
3309
+ conversation
3294
3310
  });
3295
3311
  await ctx.telemetry.recordMetric({
3296
3312
  name: __codemation_core.CodemationTelemetryMetricNames.agentTurns,
@@ -3300,30 +3316,34 @@ let AIAgentNode = class AIAgentNode$1 {
3300
3316
  name: __codemation_core.CodemationTelemetryMetricNames.agentToolCalls,
3301
3317
  value: loopResult.toolCallCount
3302
3318
  });
3303
- const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalResponse, itemScopedTools.length > 0);
3319
+ const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
3304
3320
  return this.buildOutputItem(item, outputJson);
3305
3321
  }
3306
3322
  /**
3307
- * Repeatedly invokes the model until it returns without tool calls, or guardrails end the loop.
3323
+ * Multi-turn loop:
3324
+ * - Each turn is a single `generateText` call with tools exposed but **not auto-executed**
3325
+ * (we control tool dispatch so that {@link AgentToolExecutionCoordinator} drives repair /
3326
+ * connection-invocation recording / transient-error handling exactly like before).
3327
+ * - When the model returns no tool calls the loop ends with the model's text as the final answer.
3328
+ * - Respects `guardrails.maxTurns` and `guardrails.onTurnLimitReached`.
3308
3329
  */
3309
3330
  async runTurnLoopUntilFinalAnswer(args) {
3310
- const { prepared, itemInputsByPort, itemScopedTools, conversation, modelWithTools } = args;
3311
- const { ctx, guardrails, languageModelConnectionNodeId } = prepared;
3312
- let finalResponse;
3331
+ const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
3332
+ const { ctx, guardrails } = prepared;
3333
+ let finalText = "";
3313
3334
  let toolCallCount = 0;
3314
3335
  let turnCount = 0;
3315
3336
  const repairAttemptsByToolName = /* @__PURE__ */ new Map();
3316
3337
  for (let turn = 1; turn <= guardrails.maxTurns; turn++) {
3317
3338
  turnCount = turn;
3318
- const response = await this.invokeModel(modelWithTools, languageModelConnectionNodeId, conversation, ctx, itemInputsByPort, guardrails.modelInvocationOptions);
3319
- finalResponse = response;
3320
- const toolCalls = AgentMessageFactory.extractToolCalls(response);
3321
- if (toolCalls.length === 0) break;
3339
+ const result = await this.invokeTextTurn(prepared, itemInputsByPort, conversation, itemScopedTools);
3340
+ finalText = result.text;
3341
+ if (result.toolCalls.length === 0) break;
3322
3342
  if (this.cannotExecuteAnotherToolRound(turn, guardrails)) {
3323
3343
  this.finishOrThrowWhenTurnCapHitWithToolCalls(ctx, guardrails);
3324
3344
  break;
3325
3345
  }
3326
- const plannedToolCalls = this.planToolCalls(itemScopedTools, toolCalls, ctx.nodeId);
3346
+ const plannedToolCalls = this.planToolCalls(itemScopedTools, result.toolCalls, ctx.nodeId);
3327
3347
  toolCallCount += plannedToolCalls.length;
3328
3348
  await this.markQueuedTools(plannedToolCalls, ctx);
3329
3349
  const executedToolCalls = await this.toolExecutionCoordinator.execute({
@@ -3332,11 +3352,10 @@ let AIAgentNode = class AIAgentNode$1 {
3332
3352
  agentName: this.getAgentDisplayName(ctx),
3333
3353
  repairAttemptsByToolName
3334
3354
  });
3335
- this.appendAssistantAndToolMessages(conversation, response, executedToolCalls);
3355
+ this.appendAssistantAndToolMessages(conversation, result.assistantMessage, result.text, result.toolCalls, executedToolCalls);
3336
3356
  }
3337
- if (!finalResponse) throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" did not produce a model response.`);
3338
3357
  return {
3339
- finalResponse,
3358
+ finalText,
3340
3359
  turnCount,
3341
3360
  toolCallCount
3342
3361
  };
@@ -3348,30 +3367,30 @@ let AIAgentNode = class AIAgentNode$1 {
3348
3367
  if (guardrails.onTurnLimitReached === "respondWithLastMessage") return;
3349
3368
  throw new Error(`AIAgent "${ctx.config.name ?? ctx.nodeId}" reached maxTurns=${guardrails.maxTurns} before producing a final response.`);
3350
3369
  }
3351
- appendAssistantAndToolMessages(conversation, assistantMessage, executedToolCalls) {
3352
- conversation.push(assistantMessage, ...executedToolCalls.map((toolCall) => AgentMessageFactory.createToolMessage(toolCall.toolCallId, toolCall.serialized)));
3370
+ appendAssistantAndToolMessages(conversation, assistantMessage, text, toolCalls, executedToolCalls) {
3371
+ conversation.push(assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(text, toolCalls), AgentMessageFactory.createToolResultsMessage(executedToolCalls));
3353
3372
  }
3354
- async resolveFinalOutputJson(prepared, itemInputsByPort, conversation, finalResponse, wasToolEnabledRun) {
3355
- if (!prepared.ctx.config.outputSchema) return AgentOutputFactory.fromAgentContent(AgentMessageFactory.extractContent(finalResponse));
3373
+ async resolveFinalOutputJson(prepared, itemInputsByPort, conversation, finalText, wasToolEnabledRun) {
3374
+ if (!prepared.ctx.config.outputSchema) return AgentOutputFactory.fromAgentContent(finalText);
3375
+ const conversationWithFinal = wasToolEnabledRun ? [...conversation, {
3376
+ role: "assistant",
3377
+ content: finalText
3378
+ }] : conversation;
3356
3379
  return await this.structuredOutputRunner.resolve({
3357
3380
  model: prepared.model,
3358
3381
  chatModelConfig: prepared.ctx.config.chatModel,
3359
3382
  schema: prepared.ctx.config.outputSchema,
3360
- conversation: wasToolEnabledRun ? [...conversation, finalResponse] : conversation,
3361
- rawFinalResponse: finalResponse,
3383
+ conversation: conversationWithFinal,
3384
+ rawFinalText: finalText,
3362
3385
  agentName: this.getAgentDisplayName(prepared.ctx),
3363
3386
  nodeId: prepared.ctx.nodeId,
3364
- invokeTextModel: async (messages) => await this.invokeModel(prepared.model, prepared.languageModelConnectionNodeId, messages, prepared.ctx, itemInputsByPort, prepared.guardrails.modelInvocationOptions),
3365
- invokeStructuredModel: async (structuredModel, messages) => await this.invokeStructuredModel(structuredModel, prepared.languageModelConnectionNodeId, messages, prepared.ctx, itemInputsByPort, prepared.guardrails.modelInvocationOptions)
3387
+ invokeTextModel: async (messages) => await this.invokeTextTurn(prepared, itemInputsByPort, messages, []),
3388
+ invokeStructuredModel: async (schema, messages, structuredOptions) => await this.invokeStructuredTurn(prepared, itemInputsByPort, schema, messages, structuredOptions)
3366
3389
  });
3367
3390
  }
3368
3391
  buildOutputItem(item, outputJson) {
3369
3392
  return AgentOutputFactory.replaceJson(item, outputJson);
3370
3393
  }
3371
- bindToolsToModel(model, itemScopedTools) {
3372
- if (itemScopedTools.length === 0 || !model.bindTools) return model;
3373
- return model.bindTools(itemScopedTools.map((entry) => entry.langChainTool));
3374
- }
3375
3394
  resolveTools(toolConfigs) {
3376
3395
  const resolvedTools = toolConfigs.map((config$1) => ({
3377
3396
  config: config$1,
@@ -3390,38 +3409,93 @@ let AIAgentNode = class AIAgentNode$1 {
3390
3409
  connectionNodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
3391
3410
  getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
3392
3411
  });
3393
- const langChainTool = this.executionHelpers.createDynamicStructuredTool(entry, toolCredentialContext, item, itemIndex, items);
3394
3412
  return {
3395
3413
  config: entry.config,
3396
- langChainTool
3414
+ inputSchema: entry.runtime.inputSchema,
3415
+ execute: async (input) => {
3416
+ const validated = entry.runtime.inputSchema.parse(input);
3417
+ return await entry.runtime.execute({
3418
+ config: entry.config,
3419
+ input: validated,
3420
+ ctx: toolCredentialContext,
3421
+ item,
3422
+ itemIndex,
3423
+ items
3424
+ });
3425
+ }
3397
3426
  };
3398
3427
  });
3399
3428
  }
3400
- async invokeModel(model, nodeId, messages, ctx, inputsByPort, options) {
3429
+ /**
3430
+ * Builds an AI SDK {@link ToolSet} where every tool ships a pre-converted JSON Schema (via
3431
+ * {@link jsonSchema}) — not the raw Zod schema — and carries **no** `execute`. Two reasons:
3432
+ *
3433
+ * 1. Codemation owns tool dispatch + the per-tool repair loop (see {@link AgentToolExecutionCoordinator}),
3434
+ * so the AI SDK must surface tool calls back to us instead of auto-running them.
3435
+ * 2. The AI SDK's `asSchema` helper discriminates between Zod v3 / Zod v4 / Standard Schema via
3436
+ * runtime feature-detection (`~standard`, `_zod`, etc.). Handing it a pre-built
3437
+ * {@link jsonSchema} record — which is tagged with `Symbol.for('vercel.ai.schema')` — skips all
3438
+ * of that detection and guarantees the provider receives a draft-07 JSON Schema with
3439
+ * `additionalProperties: false` at every object depth (see {@link OpenAiStrictJsonSchemaFactory}
3440
+ * for the same logic applied to structured-output schemas). Codemation still runs its own Zod
3441
+ * validation on tool inputs before execute — the schema handed to the model is advisory.
3442
+ */
3443
+ buildToolSet(itemScopedTools) {
3444
+ if (itemScopedTools.length === 0) return void 0;
3445
+ const toolSet = {};
3446
+ for (const entry of itemScopedTools) {
3447
+ const schemaRecord = this.executionHelpers.createJsonSchemaRecord(entry.inputSchema, {
3448
+ schemaName: entry.config.name,
3449
+ requireObjectRoot: true
3450
+ });
3451
+ toolSet[entry.config.name] = {
3452
+ description: entry.config.description,
3453
+ inputSchema: (0, ai.jsonSchema)(schemaRecord)
3454
+ };
3455
+ }
3456
+ return toolSet;
3457
+ }
3458
+ /**
3459
+ * One `generateText` turn (no auto tool execution) with Codemation-owned child-span telemetry
3460
+ * and connection-invocation state recording.
3461
+ */
3462
+ async invokeTextTurn(prepared, itemInputsByPort, messages, itemScopedTools) {
3401
3463
  const invocationId = __codemation_core.ConnectionInvocationIdFactory.create();
3402
3464
  const startedAt = /* @__PURE__ */ new Date();
3403
3465
  const summarizedInput = this.summarizeLlmMessages(messages);
3466
+ const { ctx, model, languageModelConnectionNodeId, guardrails } = prepared;
3404
3467
  const span = this.createModelInvocationSpan(ctx, invocationId, startedAt);
3405
3468
  await ctx.nodeState?.markQueued({
3406
- nodeId,
3469
+ nodeId: languageModelConnectionNodeId,
3407
3470
  activationId: ctx.activationId,
3408
- inputsByPort
3471
+ inputsByPort: itemInputsByPort
3409
3472
  });
3410
3473
  await ctx.nodeState?.markRunning({
3411
- nodeId,
3474
+ nodeId: languageModelConnectionNodeId,
3412
3475
  activationId: ctx.activationId,
3413
- inputsByPort
3476
+ inputsByPort: itemInputsByPort
3414
3477
  });
3415
3478
  try {
3416
- const response = await model.invoke(messages, options);
3479
+ const tools = this.buildToolSet(itemScopedTools);
3480
+ const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
3481
+ const result = await (0, ai.generateText)({
3482
+ model: model.languageModel,
3483
+ messages: [...messages],
3484
+ tools,
3485
+ toolChoice: tools ? "auto" : void 0,
3486
+ maxOutputTokens: callOptions.maxOutputTokens,
3487
+ temperature: callOptions.temperature,
3488
+ providerOptions: callOptions.providerOptions,
3489
+ maxRetries: 0
3490
+ });
3491
+ const turnResult = this.extractTurnResult(result);
3417
3492
  const finishedAt = /* @__PURE__ */ new Date();
3418
3493
  await ctx.nodeState?.markCompleted({
3419
- nodeId,
3494
+ nodeId: languageModelConnectionNodeId,
3420
3495
  activationId: ctx.activationId,
3421
- inputsByPort,
3422
- outputs: AgentOutputFactory.fromUnknown({ content: AgentMessageFactory.extractContent(response) })
3496
+ inputsByPort: itemInputsByPort,
3497
+ outputs: AgentOutputFactory.fromUnknown({ content: turnResult.text })
3423
3498
  });
3424
- const content = AgentMessageFactory.extractContent(response);
3425
3499
  await span.attachArtifact({
3426
3500
  kind: "ai.messages",
3427
3501
  contentType: "application/json",
@@ -3430,26 +3504,26 @@ let AIAgentNode = class AIAgentNode$1 {
3430
3504
  await span.attachArtifact({
3431
3505
  kind: "ai.response",
3432
3506
  contentType: "application/json",
3433
- previewJson: content
3507
+ previewJson: turnResult.text
3434
3508
  });
3435
- await this.recordModelUsageMetrics(span, response, ctx);
3509
+ await this.recordModelUsageMetrics(span, turnResult.usage, ctx);
3436
3510
  await span.end({
3437
3511
  status: "ok",
3438
3512
  endedAt: finishedAt
3439
3513
  });
3440
3514
  await ctx.nodeState?.appendConnectionInvocation({
3441
3515
  invocationId,
3442
- connectionNodeId: nodeId,
3516
+ connectionNodeId: languageModelConnectionNodeId,
3443
3517
  parentAgentNodeId: ctx.nodeId,
3444
3518
  parentAgentActivationId: ctx.activationId,
3445
3519
  status: "completed",
3446
3520
  managedInput: summarizedInput,
3447
- managedOutput: content,
3521
+ managedOutput: turnResult.text,
3448
3522
  queuedAt: startedAt.toISOString(),
3449
3523
  startedAt: startedAt.toISOString(),
3450
3524
  finishedAt: finishedAt.toISOString()
3451
3525
  });
3452
- return response;
3526
+ return turnResult;
3453
3527
  } catch (error) {
3454
3528
  await span.end({
3455
3529
  status: "error",
@@ -3460,36 +3534,53 @@ let AIAgentNode = class AIAgentNode$1 {
3460
3534
  error,
3461
3535
  invocationId,
3462
3536
  startedAt,
3463
- nodeId,
3537
+ nodeId: languageModelConnectionNodeId,
3464
3538
  ctx,
3465
- inputsByPort,
3466
- managedInput: this.summarizeLlmMessages(messages)
3539
+ inputsByPort: itemInputsByPort,
3540
+ managedInput: summarizedInput
3467
3541
  });
3468
3542
  }
3469
3543
  }
3470
- async invokeStructuredModel(model, nodeId, messages, ctx, inputsByPort, options) {
3544
+ /**
3545
+ * Structured-output turn: runs `generateText({ output: Output.object({ schema }) })` via the
3546
+ * structured-output runner. We keep this as a separate helper because the runner needs the raw
3547
+ * validated value (not just text) back, and must be able to retry on Zod failures.
3548
+ */
3549
+ async invokeStructuredTurn(prepared, itemInputsByPort, schema, messages, structuredOptions) {
3471
3550
  const invocationId = __codemation_core.ConnectionInvocationIdFactory.create();
3472
3551
  const startedAt = /* @__PURE__ */ new Date();
3473
3552
  const summarizedInput = this.summarizeLlmMessages(messages);
3553
+ const { ctx, model, languageModelConnectionNodeId, guardrails } = prepared;
3474
3554
  const span = this.createModelInvocationSpan(ctx, invocationId, startedAt);
3475
3555
  await ctx.nodeState?.markQueued({
3476
- nodeId,
3556
+ nodeId: languageModelConnectionNodeId,
3477
3557
  activationId: ctx.activationId,
3478
- inputsByPort
3558
+ inputsByPort: itemInputsByPort
3479
3559
  });
3480
3560
  await ctx.nodeState?.markRunning({
3481
- nodeId,
3561
+ nodeId: languageModelConnectionNodeId,
3482
3562
  activationId: ctx.activationId,
3483
- inputsByPort
3563
+ inputsByPort: itemInputsByPort
3484
3564
  });
3485
3565
  try {
3486
- const response = await model.invoke(messages, options);
3566
+ const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
3567
+ const outputSchema = structuredOptions?.strict && !this.isZodSchema(schema) ? ai.Output.object({ schema: (0, ai.jsonSchema)(schema) }) : ai.Output.object({ schema });
3568
+ const result = await (0, ai.generateText)({
3569
+ model: model.languageModel,
3570
+ messages: [...messages],
3571
+ experimental_output: outputSchema,
3572
+ maxOutputTokens: callOptions.maxOutputTokens,
3573
+ temperature: callOptions.temperature,
3574
+ providerOptions: callOptions.providerOptions,
3575
+ maxRetries: 0
3576
+ });
3577
+ const turnResult = this.extractTurnResult(result);
3487
3578
  const finishedAt = /* @__PURE__ */ new Date();
3488
3579
  await ctx.nodeState?.markCompleted({
3489
- nodeId,
3580
+ nodeId: languageModelConnectionNodeId,
3490
3581
  activationId: ctx.activationId,
3491
- inputsByPort,
3492
- outputs: AgentOutputFactory.fromUnknown(response)
3582
+ inputsByPort: itemInputsByPort,
3583
+ outputs: AgentOutputFactory.fromUnknown(result.experimental_output)
3493
3584
  });
3494
3585
  await span.attachArtifact({
3495
3586
  kind: "ai.messages",
@@ -3499,26 +3590,26 @@ let AIAgentNode = class AIAgentNode$1 {
3499
3590
  await span.attachArtifact({
3500
3591
  kind: "ai.response.structured",
3501
3592
  contentType: "application/json",
3502
- previewJson: this.resultToJsonValue(response)
3593
+ previewJson: this.resultToJsonValue(result.experimental_output)
3503
3594
  });
3504
- await this.recordModelUsageMetrics(span, response, ctx);
3595
+ await this.recordModelUsageMetrics(span, turnResult.usage, ctx);
3505
3596
  await span.end({
3506
3597
  status: "ok",
3507
3598
  endedAt: finishedAt
3508
3599
  });
3509
3600
  await ctx.nodeState?.appendConnectionInvocation({
3510
3601
  invocationId,
3511
- connectionNodeId: nodeId,
3602
+ connectionNodeId: languageModelConnectionNodeId,
3512
3603
  parentAgentNodeId: ctx.nodeId,
3513
3604
  parentAgentActivationId: ctx.activationId,
3514
3605
  status: "completed",
3515
3606
  managedInput: summarizedInput,
3516
- managedOutput: this.resultToJsonValue(response),
3607
+ managedOutput: this.resultToJsonValue(result.experimental_output),
3517
3608
  queuedAt: startedAt.toISOString(),
3518
3609
  startedAt: startedAt.toISOString(),
3519
3610
  finishedAt: finishedAt.toISOString()
3520
3611
  });
3521
- return response;
3612
+ return result.experimental_output;
3522
3613
  } catch (error) {
3523
3614
  await span.end({
3524
3615
  status: "error",
@@ -3529,13 +3620,59 @@ let AIAgentNode = class AIAgentNode$1 {
3529
3620
  error,
3530
3621
  invocationId,
3531
3622
  startedAt,
3532
- nodeId,
3623
+ nodeId: languageModelConnectionNodeId,
3533
3624
  ctx,
3534
- inputsByPort,
3535
- managedInput: this.summarizeLlmMessages(messages)
3625
+ inputsByPort: itemInputsByPort,
3626
+ managedInput: summarizedInput
3536
3627
  });
3537
3628
  }
3538
3629
  }
3630
+ isZodSchema(schema) {
3631
+ return typeof schema.parse === "function";
3632
+ }
3633
+ resolveCallOptions(model, overrides) {
3634
+ const defaults = model.defaultCallOptions ?? {};
3635
+ return {
3636
+ maxOutputTokens: overrides?.maxTokens ?? defaults.maxOutputTokens,
3637
+ temperature: defaults.temperature,
3638
+ providerOptions: overrides?.providerOptions ?? defaults.providerOptions
3639
+ };
3640
+ }
3641
+ extractTurnResult(result) {
3642
+ const usage = this.extractUsageFromResult(result);
3643
+ const text = result.text;
3644
+ const toolCalls = result.toolCalls.map((toolCall) => ({
3645
+ id: toolCall.toolCallId,
3646
+ name: toolCall.toolName,
3647
+ input: toolCall.input
3648
+ }));
3649
+ return {
3650
+ assistantMessage: this.extractAssistantMessage(result),
3651
+ text,
3652
+ toolCalls,
3653
+ usage
3654
+ };
3655
+ }
3656
+ extractAssistantMessage(result) {
3657
+ const assistantMessages = result.response.messages.filter((m) => m.role === "assistant");
3658
+ return assistantMessages[assistantMessages.length - 1];
3659
+ }
3660
+ extractUsageFromResult(result) {
3661
+ const usage = result.usage;
3662
+ const inputTokens = this.toFiniteNumber(usage.inputTokens);
3663
+ const outputTokens = this.toFiniteNumber(usage.outputTokens);
3664
+ return {
3665
+ inputTokens,
3666
+ outputTokens,
3667
+ totalTokens: this.toFiniteNumber(usage.totalTokens) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
3668
+ cachedInputTokens: this.toFiniteNumber(usage.cachedInputTokens),
3669
+ reasoningTokens: this.toFiniteNumber(usage.reasoningTokens)
3670
+ };
3671
+ }
3672
+ toFiniteNumber(value) {
3673
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
3674
+ return value;
3675
+ }
3539
3676
  createModelInvocationSpan(ctx, invocationId, startedAt) {
3540
3677
  return ctx.telemetry.startChildSpan({
3541
3678
  name: "gen_ai.chat.completion",
@@ -3548,9 +3685,15 @@ let AIAgentNode = class AIAgentNode$1 {
3548
3685
  }
3549
3686
  });
3550
3687
  }
3551
- async recordModelUsageMetrics(span, response, ctx) {
3552
- const usage = this.extractModelUsageMetrics(response);
3553
- for (const [name, value] of Object.entries(usage)) {
3688
+ async recordModelUsageMetrics(span, usage, ctx) {
3689
+ const entries = [
3690
+ [__codemation_core.GenAiTelemetryAttributeNames.usageInputTokens, usage.inputTokens],
3691
+ [__codemation_core.GenAiTelemetryAttributeNames.usageOutputTokens, usage.outputTokens],
3692
+ [__codemation_core.GenAiTelemetryAttributeNames.usageTotalTokens, usage.totalTokens],
3693
+ [__codemation_core.GenAiTelemetryAttributeNames.usageCacheReadInputTokens, usage.cachedInputTokens],
3694
+ [__codemation_core.GenAiTelemetryAttributeNames.usageReasoningTokens, usage.reasoningTokens]
3695
+ ];
3696
+ for (const [name, value] of entries) {
3554
3697
  if (value === void 0) continue;
3555
3698
  await span.recordMetric({
3556
3699
  name,
@@ -3565,93 +3708,28 @@ let AIAgentNode = class AIAgentNode$1 {
3565
3708
  const provider = ctx.config.chatModel.provider;
3566
3709
  const pricingKey = ctx.config.chatModel.modelName;
3567
3710
  if (!provider || !pricingKey) return;
3568
- const inputTokens = usage[__codemation_core.GenAiTelemetryAttributeNames.usageInputTokens];
3569
- const outputTokens = usage[__codemation_core.GenAiTelemetryAttributeNames.usageOutputTokens];
3570
- if (inputTokens !== void 0) await costTracking.captureUsage({
3711
+ if (usage.inputTokens !== void 0) await costTracking.captureUsage({
3571
3712
  component: "chat",
3572
3713
  provider,
3573
3714
  operation: "completion.input",
3574
3715
  pricingKey,
3575
3716
  usageUnit: "input_tokens",
3576
- quantity: inputTokens,
3717
+ quantity: usage.inputTokens,
3577
3718
  modelName: pricingKey
3578
3719
  });
3579
- if (outputTokens !== void 0) await costTracking.captureUsage({
3720
+ if (usage.outputTokens !== void 0) await costTracking.captureUsage({
3580
3721
  component: "chat",
3581
3722
  provider,
3582
3723
  operation: "completion.output",
3583
3724
  pricingKey,
3584
3725
  usageUnit: "output_tokens",
3585
- quantity: outputTokens,
3726
+ quantity: usage.outputTokens,
3586
3727
  modelName: pricingKey
3587
3728
  });
3588
3729
  }
3589
3730
  resolveChatModelName(chatModel$1) {
3590
3731
  return chatModel$1.modelName ?? chatModel$1.name;
3591
3732
  }
3592
- extractModelUsageMetrics(response) {
3593
- const usage = this.extractUsageObject(response);
3594
- const inputTokens = this.readUsageNumber(usage, [
3595
- "input_tokens",
3596
- "inputTokens",
3597
- "prompt_tokens",
3598
- "promptTokens"
3599
- ]);
3600
- const outputTokens = this.readUsageNumber(usage, [
3601
- "output_tokens",
3602
- "outputTokens",
3603
- "completion_tokens",
3604
- "completionTokens"
3605
- ]);
3606
- const totalTokens = this.readUsageNumber(usage, ["total_tokens", "totalTokens"]) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0);
3607
- const cachedInputTokens = this.readUsageNumber(usage, [
3608
- "cache_read_input_tokens",
3609
- "cacheReadInputTokens",
3610
- "input_token_details.cached_tokens"
3611
- ]);
3612
- const reasoningTokens = this.readUsageNumber(usage, [
3613
- "reasoning_tokens",
3614
- "reasoningTokens",
3615
- "output_token_details.reasoning_tokens"
3616
- ]);
3617
- return {
3618
- [__codemation_core.GenAiTelemetryAttributeNames.usageInputTokens]: inputTokens,
3619
- [__codemation_core.GenAiTelemetryAttributeNames.usageOutputTokens]: outputTokens,
3620
- [__codemation_core.GenAiTelemetryAttributeNames.usageTotalTokens]: totalTokens,
3621
- [__codemation_core.GenAiTelemetryAttributeNames.usageCacheReadInputTokens]: cachedInputTokens,
3622
- [__codemation_core.GenAiTelemetryAttributeNames.usageReasoningTokens]: reasoningTokens
3623
- };
3624
- }
3625
- extractUsageObject(response) {
3626
- if (!this.isRecord(response)) return;
3627
- const usageMetadata = response["usage_metadata"];
3628
- if (this.isRecord(usageMetadata)) return usageMetadata;
3629
- const responseMetadata = response["response_metadata"];
3630
- if (this.isRecord(responseMetadata)) {
3631
- const tokenUsage = responseMetadata["tokenUsage"];
3632
- if (this.isRecord(tokenUsage)) return tokenUsage;
3633
- const usage = responseMetadata["usage"];
3634
- if (this.isRecord(usage)) return usage;
3635
- }
3636
- }
3637
- readUsageNumber(source, keys) {
3638
- for (const key of keys) {
3639
- const value = this.readNestedUsageValue(source, key);
3640
- if (typeof value === "number" && Number.isFinite(value)) return value;
3641
- }
3642
- }
3643
- readNestedUsageValue(source, dottedKey) {
3644
- if (!source) return;
3645
- let current = source;
3646
- for (const segment of dottedKey.split(".")) {
3647
- if (!this.isRecord(current)) return;
3648
- current = current[segment];
3649
- }
3650
- return current;
3651
- }
3652
- isRecord(value) {
3653
- return typeof value === "object" && value !== null;
3654
- }
3655
3733
  async markQueuedTools(plannedToolCalls, ctx) {
3656
3734
  for (const plannedToolCall of plannedToolCalls) await ctx.nodeState?.markQueued({
3657
3735
  nodeId: plannedToolCall.nodeId,
@@ -3711,7 +3789,7 @@ let AIAgentNode = class AIAgentNode$1 {
3711
3789
  };
3712
3790
  }
3713
3791
  resultToJsonValue(value) {
3714
- if (value === void 0) return;
3792
+ if (value === void 0) return void 0;
3715
3793
  const json = JSON.stringify(value);
3716
3794
  return JSON.parse(json);
3717
3795
  }
@@ -3726,7 +3804,7 @@ let AIAgentNode = class AIAgentNode$1 {
3726
3804
  resolveToolRuntime(config$1) {
3727
3805
  if (this.isNodeBackedToolConfig(config$1)) {
3728
3806
  const inputSchema = config$1.getInputSchema();
3729
- if (inputSchema == null) throw new Error(`AIAgent tool "${config$1.name}": node-backed tool is missing inputSchema (cannot build LangChain tool).`);
3807
+ if (inputSchema == null) throw new Error(`AIAgent tool "${config$1.name}": node-backed tool is missing inputSchema (cannot build AI SDK tool).`);
3730
3808
  return {
3731
3809
  defaultDescription: `Run workflow node "${config$1.node.name ?? config$1.name}" as an AI tool.`,
3732
3810
  inputSchema,
@@ -3735,7 +3813,7 @@ let AIAgentNode = class AIAgentNode$1 {
3735
3813
  }
3736
3814
  if (this.isCallableToolConfig(config$1)) {
3737
3815
  const inputSchema = config$1.getInputSchema();
3738
- if (inputSchema == null) throw new Error(`AIAgent tool "${config$1.name}": callable tool is missing inputSchema (cannot build LangChain tool).`);
3816
+ if (inputSchema == null) throw new Error(`AIAgent tool "${config$1.name}": callable tool is missing inputSchema (cannot build AI SDK tool).`);
3739
3817
  return {
3740
3818
  defaultDescription: config$1.description ?? `Callable tool "${config$1.name}".`,
3741
3819
  inputSchema,
@@ -3753,17 +3831,9 @@ let AIAgentNode = class AIAgentNode$1 {
3753
3831
  execute: async (args) => await Promise.resolve(tool.execute(args))
3754
3832
  };
3755
3833
  }
3756
- /**
3757
- * Consumer apps can resolve two copies of `@codemation/core`, breaking `instanceof NodeBackedToolConfig` and
3758
- * sending node-backed tools down the plugin-tool branch with `inputSchema: undefined` (LangChain then crashes in
3759
- * json-schema validation). {@link NodeBackedToolConfig#toolKind} is stable across copies.
3760
- */
3761
3834
  isNodeBackedToolConfig(config$1) {
3762
3835
  return config$1 instanceof __codemation_core.NodeBackedToolConfig || typeof config$1 === "object" && config$1 !== null && config$1.toolKind === "nodeBacked";
3763
3836
  }
3764
- /**
3765
- * Callable tools use {@link CallableToolConfig#toolKind} for cross-package / JSON round-trip safety.
3766
- */
3767
3837
  isCallableToolConfig(config$1) {
3768
3838
  return config$1 instanceof __codemation_core.CallableToolConfig || typeof config$1 === "object" && config$1 !== null && config$1.toolKind === "callable";
3769
3839
  }
@@ -4983,13 +5053,13 @@ Object.defineProperty(exports, 'OpenAIChatModelFactory', {
4983
5053
  return OpenAIChatModelFactory;
4984
5054
  }
4985
5055
  });
4986
- Object.defineProperty(exports, 'OpenAIStructuredOutputMethodFactory', {
5056
+ exports.OpenAiChatModelPresets = OpenAiChatModelPresets;
5057
+ Object.defineProperty(exports, 'OpenAiStrictJsonSchemaFactory', {
4987
5058
  enumerable: true,
4988
5059
  get: function () {
4989
- return OpenAIStructuredOutputMethodFactory;
5060
+ return OpenAiStrictJsonSchemaFactory;
4990
5061
  }
4991
5062
  });
4992
- exports.OpenAiChatModelPresets = OpenAiChatModelPresets;
4993
5063
  exports.Split = Split;
4994
5064
  Object.defineProperty(exports, 'SplitNode', {
4995
5065
  enumerable: true,