@kortyx/hooks 0.32.0 → 0.34.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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.34.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.33.0...hooks-v0.34.0) (2026-10-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * add shared SDK and Studio usage telemetry ([#313](https://github.com/kortyx-io/kortyx/issues/313)) ([7d5f0c0](https://github.com/kortyx-io/kortyx/commit/7d5f0c005bd7721dc6a529554e75c14573d76a0f))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **website:** reconcile credibility branch with current main ([60199f9](https://github.com/kortyx-io/kortyx/commit/60199f992242eb7c409a95bc7a6898d31bac601d))
14
+
15
+
16
+ ### Dependencies
17
+
18
+ * The following workspace dependencies were updated
19
+ * dependencies
20
+ * @kortyx/core bumped to 0.13.1
21
+ * @kortyx/providers bumped to 0.9.2
22
+ * @kortyx/telemetry-contracts bumped to 0.17.0
23
+ * @kortyx/prompts bumped to 0.2.0
24
+ * @kortyx/usage-telemetry bumped to 0.2.0
25
+
26
+ ## [0.33.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.32.1...hooks-v0.33.0) (2026-10-10)
27
+
28
+
29
+ ### Features
30
+
31
+ * version, evaluate, serve and migrate Studio prompts ([#288](https://github.com/kortyx-io/kortyx/issues/288)) ([0802f8b](https://github.com/kortyx-io/kortyx/commit/0802f8b91c4a541d176dd8a75c659063e2c28c8c))
32
+
33
+
34
+ ### Dependencies
35
+
36
+ * The following workspace dependencies were updated
37
+ * dependencies
38
+ * @kortyx/telemetry-contracts bumped to 0.16.0
39
+ * @kortyx/prompts bumped to 0.1.0
40
+
41
+ ## [0.32.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.32.0...hooks-v0.32.1) (2026-10-09)
42
+
43
+
44
+ ### Dependencies
45
+
46
+ * The following workspace dependencies were updated
47
+ * dependencies
48
+ * @kortyx/core bumped to 0.13.0
49
+ * @kortyx/providers bumped to 0.9.1
50
+
3
51
  ## [0.32.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.31.0...hooks-v0.32.0) (2026-10-08)
4
52
 
5
53
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as _kortyx_core from '@kortyx/core';
2
2
  import { WorkflowDefinition, TokenUsage, InterruptInput, GraphState, NodeConfig, InterruptResumeValue, InterruptResult } from '@kortyx/core';
3
+ import * as _kortyx_prompts from '@kortyx/prompts';
4
+ import { CompiledPrompt, PromptRef } from '@kortyx/prompts';
3
5
  import { K as KortyxTelemetryConfig, R as ReasonTraceAdapter, a as KortyxTraceMetadata, b as ReportErrorOptions, c as KortyxTelemetryEventType } from './tracing-mXE0Euvx.js';
4
6
  export { E as EnsureWorkflowTopologyRequest, d as EnsureWorkflowTopologyResponse, e as KortyxErrorDetails, f as KortyxTelemetryContentCapture, g as KortyxTelemetryCorrelation, h as KortyxTelemetryEvent, i as KortyxTelemetryPrompt, j as KortyxTelemetryReporter, k as KortyxTelemetryService, l as KortyxTraceAdapter, m as KortyxTraceErrorProjection, n as ReasonTraceAttributes, o as ReasonTraceSpan, p as ReasonTraceSpanEndArgs, q as ReasonTraceSpanStartArgs } from './tracing-mXE0Euvx.js';
5
7
  import { FailureDescriptor, KortyxError } from '@kortyx/core/errors';
@@ -12,7 +14,6 @@ type CompleteResponseOptions = {
12
14
  message?: string | undefined;
13
15
  data?: unknown;
14
16
  };
15
- /** Finish client delivery, leaving the node and workflow execution running. */
16
17
  declare function completeResponse(options?: CompleteResponseOptions): Promise<void>;
17
18
 
18
19
  /** All siblings have settled; results retain the order passed to parallel(). */
@@ -85,6 +86,7 @@ declare function useWorkflow(args: {
85
86
  }>;
86
87
 
87
88
  type HookNodeRuntimeContext = {
89
+ prompts?: _kortyx_prompts.PromptExecution | undefined;
88
90
  completeResponse?: ((options: CompleteResponseOptions, state: GraphState) => Promise<void>) | undefined;
89
91
  /** Transient execution control; never part of checkpointed state. */
90
92
  abortSignal?: AbortSignal | undefined;
@@ -267,6 +269,7 @@ type UseReasonToolExecution = {
267
269
  };
268
270
  type UseReasonArgs<TOutput = unknown, TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult, TContracts extends InterruptContractMap = InterruptContractMap, TEmit extends OutputContractMap = OutputContractMap, TReturn extends OutputContractMap = OutputContractMap> = {
269
271
  model: ProviderModelRef;
272
+ messages?: _kortyx_providers.KortyxPromptMessage[] | undefined;
270
273
  input: string;
271
274
  system?: string | undefined;
272
275
  temperature?: number | undefined;
@@ -346,7 +349,12 @@ declare const defineOutputContract: <TData>(contract: OutputContract<TData>) =>
346
349
  type StateSetter<T> = (next: T | ((prev: T) => T)) => void;
347
350
  declare function useStructuredData<TData>(args: UseContractStructuredDataArgs<TData>): void;
348
351
  declare function useStructuredData<TData = unknown>(args: UseStructuredDataArgs<TData>): void;
349
- declare function useReason<TOutput = unknown, TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult, TContracts extends InterruptContractMap = InterruptContractMap, TEmit extends OutputContractMap = OutputContractMap, TReturn extends OutputContractMap = OutputContractMap>(args: UseReasonArgs<TOutput, TRequest, TResponse, TContracts, TEmit, TReturn>): Promise<UseReasonResult<TOutput, TResponse, TContracts, TEmit, TReturn>>;
352
+ declare function useReason<TOutput = unknown, TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult, TContracts extends InterruptContractMap = InterruptContractMap, TEmit extends OutputContractMap = OutputContractMap, TReturn extends OutputContractMap = OutputContractMap>(args: UseReasonArgs<TOutput, TRequest, TResponse, TContracts, TEmit, TReturn> | (Omit<UseReasonArgs<TOutput, TRequest, TResponse, TContracts, TEmit, TReturn>, "input" | "system" | "messages"> & {
353
+ prompt: CompiledPrompt;
354
+ input?: never;
355
+ system?: never;
356
+ messages?: never;
357
+ })): Promise<UseReasonResult<TOutput, TResponse, TContracts, TEmit, TReturn>>;
350
358
  declare function useInterrupt<TRequest, TResponse>(args: UseContractInterruptArgs<TRequest, TResponse>): Promise<TResponse>;
351
359
  declare function useInterrupt<TRequest extends InterruptInput = InterruptInput, TResponse = InterruptResult>(args: UseInterruptArgs<TRequest, TResponse>): Promise<TResponse>;
352
360
  /** Live execution signal for cooperative I/O. Never store it in workflow state. */
@@ -355,6 +363,12 @@ declare function useRuntimeContext<TContext extends Record<string, unknown> = Re
355
363
  declare function useNodeState<T>(initialValue: T): [T, StateSetter<T>];
356
364
  declare function useWorkflowState<T>(key: string, ...rest: [] | [T]): [T, StateSetter<T>];
357
365
 
366
+ type UseReasonPromptArgs = Omit<UseReasonArgs, "input" | "system" | "messages"> & {
367
+ prompt: CompiledPrompt;
368
+ input?: never;
369
+ system?: never;
370
+ };
371
+
358
372
  /** Reports a handled error to the configured observer without stopping execution. */
359
373
  declare function reportError(error: unknown, options?: ReportErrorOptions): void;
360
374
 
@@ -366,7 +380,13 @@ type UseToolArgs<T extends KortyxExecutableTool> = {
366
380
  };
367
381
  declare function useTool<T extends KortyxExecutableTool>(args: UseToolArgs<T>): Promise<Awaited<ReturnType<T["execute"]>>>;
368
382
 
383
+ /** Resolve both roles from one immutable version in the current execution. */
384
+ declare function usePrompt<Inputs, Config>(reference: PromptRef<Inputs, Config>, options: {
385
+ variables: Inputs;
386
+ version?: number;
387
+ }): Promise<CompiledPrompt<Config>>;
388
+
369
389
  /** Best effort lifecycle facts. Engine snapshots and interrupt tokens never leave the runtime. */
370
390
  declare function emitWorkflowCall(telemetry: KortyxTelemetryConfig | undefined, type: KortyxTelemetryEventType, payload: Record<string, unknown>): void;
371
391
 
372
- export { type CompleteResponseOptions, type HookRuntimeContext, type InterruptContract, type InterruptContractMap, type InterruptHistoryEntry, KortyxTelemetryConfig, KortyxTelemetryEventType, KortyxTraceMetadata, type OutputContract, type OutputContractEntry, type OutputContractMap, ParallelError, ReasonTraceAdapter, ReportErrorOptions, type UseContractInterruptArgs, type UseContractStructuredDataArgs, type UseInterruptArgs, type UseReasonArgs, type UseReasonInterruptConfig, type UseReasonInterruptsConfig, type UseReasonOutputsConfig, type UseReasonResult, type UseReasonStep, type UseReasonToolExecution, type UseStructuredDataArgs, type UseToolArgs, WorkflowCallError, type WorkflowCallOutcome, type WorkflowCallService, completeResponse, createWorkflowHooks, defineInterruptContract, defineOutputContract, emitWorkflowCall, parallel, reportError, runWithHookContext, useAbortSignal, useInterrupt, useNodeState, useReason, useRuntimeContext, useStructuredData, useTool, useWorkflow, useWorkflowState, workflowCallFingerprint };
392
+ export { type CompleteResponseOptions, type HookRuntimeContext, type InterruptContract, type InterruptContractMap, type InterruptHistoryEntry, KortyxTelemetryConfig, KortyxTelemetryEventType, KortyxTraceMetadata, type OutputContract, type OutputContractEntry, type OutputContractMap, ParallelError, ReasonTraceAdapter, ReportErrorOptions, type UseContractInterruptArgs, type UseContractStructuredDataArgs, type UseInterruptArgs, type UseReasonArgs, type UseReasonInterruptConfig, type UseReasonInterruptsConfig, type UseReasonOutputsConfig, type UseReasonPromptArgs, type UseReasonResult, type UseReasonStep, type UseReasonToolExecution, type UseStructuredDataArgs, type UseToolArgs, WorkflowCallError, type WorkflowCallOutcome, type WorkflowCallService, completeResponse, createWorkflowHooks, defineInterruptContract, defineOutputContract, emitWorkflowCall, parallel, reportError, runWithHookContext, useAbortSignal, useInterrupt, useNodeState, usePrompt, useReason, useRuntimeContext, useStructuredData, useTool, useWorkflow, useWorkflowState, workflowCallFingerprint };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  useAbortSignal: () => useAbortSignal,
33
33
  useInterrupt: () => useInterrupt,
34
34
  useNodeState: () => useNodeState,
35
+ usePrompt: () => usePrompt,
35
36
  useReason: () => useReason2,
36
37
  useRuntimeContext: () => useRuntimeContext,
37
38
  useStructuredData: () => useStructuredData,
@@ -137,11 +138,11 @@ var cleanupCompletedReasonCheckpoints = (ctx) => {
137
138
  }
138
139
  if (changed) ctx.stateDirty = true;
139
140
  };
140
- var toTokenUsageDelta = (usage) => {
141
- if (!usage) return void 0;
142
- const input = usage.input ?? 0;
143
- const output = usage.output ?? 0;
144
- const total = usage.total ?? input + output + (usage.outputIncludesReasoning ? 0 : usage.reasoning ?? 0);
141
+ var toTokenUsageDelta = (usage2) => {
142
+ if (!usage2) return void 0;
143
+ const input = usage2.input ?? 0;
144
+ const output = usage2.output ?? 0;
145
+ const total = usage2.total ?? input + output + (usage2.outputIncludesReasoning ? 0 : usage2.reasoning ?? 0);
145
146
  if (input === 0 && output === 0 && total === 0) {
146
147
  return void 0;
147
148
  }
@@ -151,8 +152,8 @@ var toTokenUsageDelta = (usage) => {
151
152
  total
152
153
  };
153
154
  };
154
- function accumulateTokenUsage(usage) {
155
- const delta = toTokenUsageDelta(usage);
155
+ function accumulateTokenUsage(usage2) {
156
+ const delta = toTokenUsageDelta(usage2);
156
157
  if (!delta) return;
157
158
  const ctx = getHookContext();
158
159
  const current = ctx.tokenUsage ?? {
@@ -238,12 +239,98 @@ function snapshotHookState() {
238
239
  };
239
240
  }
240
241
 
242
+ // src/usage.ts
243
+ var import_node = require("@kortyx/usage-telemetry/node");
244
+
245
+ // package.json
246
+ var package_default = {
247
+ name: "@kortyx/hooks",
248
+ version: "0.34.0",
249
+ private: false,
250
+ description: "Lifecycle hooks and extension points for Kortyx.",
251
+ keywords: [
252
+ "ai",
253
+ "agent",
254
+ "agents",
255
+ "orchestration",
256
+ "framework",
257
+ "hooks",
258
+ "kortyx"
259
+ ],
260
+ license: "Apache-2.0",
261
+ homepage: "https://kortyx.io/",
262
+ bugs: {
263
+ url: "https://github.com/kortyx-io/kortyx/issues"
264
+ },
265
+ author: "Kortyx (https://github.com/kortyx-io)",
266
+ main: "./dist/index.js",
267
+ types: "./dist/index.d.ts",
268
+ exports: {
269
+ ".": {
270
+ types: "./dist/index.d.ts",
271
+ default: "./dist/index.js"
272
+ },
273
+ "./internal": {
274
+ types: "./dist/internal.d.ts",
275
+ default: "./dist/internal.js"
276
+ }
277
+ },
278
+ files: [
279
+ "dist/**",
280
+ "README.md",
281
+ "CHANGELOG.md"
282
+ ],
283
+ scripts: {
284
+ build: "tsup",
285
+ predev: "tsc -p tsconfig.build.json",
286
+ dev: "tsc --watch",
287
+ lint: "biome check src",
288
+ test: "vitest run && pnpm -s type-check",
289
+ coverage: "vitest run --coverage && pnpm -s type-check",
290
+ "test:watch": "vitest",
291
+ "type-check": "tsc --noEmit -p tsconfig.test.json"
292
+ },
293
+ dependencies: {
294
+ "@kortyx/core": "workspace:*",
295
+ "@kortyx/providers": "workspace:*",
296
+ "@kortyx/telemetry-contracts": "workspace:*",
297
+ zod: "catalog:",
298
+ "@kortyx/prompts": "workspace:*",
299
+ "@kortyx/usage-telemetry": "workspace:*"
300
+ },
301
+ devDependencies: {
302
+ turbo: "catalog:",
303
+ typescript: "catalog:",
304
+ tsup: "catalog:",
305
+ vitest: "catalog:",
306
+ "@vitest/coverage-v8": "catalog:"
307
+ },
308
+ sideEffects: false,
309
+ repository: {
310
+ type: "git",
311
+ url: "https://github.com/kortyx-io/kortyx.git",
312
+ directory: "packages/hooks"
313
+ },
314
+ publishConfig: {
315
+ access: "public"
316
+ }
317
+ };
318
+
319
+ // src/usage.ts
320
+ var client = () => (0, import_node.getUsageTelemetry)("hooks", package_default.version);
321
+ var usage = {
322
+ start: (...args) => client().start(...args),
323
+ feature: (...args) => client().feature(...args),
324
+ measure: (...args) => client().measure(...args),
325
+ flush: () => client().flush()
326
+ };
327
+
241
328
  // src/complete-response.ts
242
329
  var optionsSchema = import_zod.z.object({
243
330
  message: import_zod.z.string().optional(),
244
331
  data: import_zod.z.json().optional()
245
332
  }).strict();
246
- async function completeResponse(options = {}) {
333
+ async function completeResponseInternal(options = {}) {
247
334
  const ctx = getHookContext();
248
335
  const parsed = optionsSchema.parse(options);
249
336
  if (!ctx.node.completeResponse) {
@@ -253,6 +340,12 @@ async function completeResponse(options = {}) {
253
340
  }
254
341
  await ctx.node.completeResponse(parsed, snapshotHookState());
255
342
  }
343
+ function completeResponse(options = {}) {
344
+ return usage.measure(
345
+ "sdk.complete_response",
346
+ () => completeResponseInternal(options)
347
+ );
348
+ }
256
349
 
257
350
  // src/interrupt.ts
258
351
  var import_core = require("@kortyx/core");
@@ -651,7 +744,7 @@ var createStructuredStreamId = () => {
651
744
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
652
745
  }
653
746
  };
654
- var emitStructuredData = (args) => {
747
+ var emitStructuredDataInternal = (args) => {
655
748
  const ctx = getHookContext();
656
749
  const streamId = typeof args.streamId === "string" && args.streamId.length > 0 ? args.streamId : createStructuredStreamId();
657
750
  const dataType = typeof args.dataType === "string" && args.dataType.length > 0 ? args.dataType : "generic";
@@ -714,6 +807,16 @@ var emitStructuredData = (args) => {
714
807
  };
715
808
  var shouldEmitStructured = (cfg) => Boolean(cfg);
716
809
  var shouldStreamStructured = (cfg) => Boolean(cfg) && (cfg?.stream ?? true);
810
+ var emitStructuredData = (args) => {
811
+ const scope = usage.start("sdk.structured", { format: args.kind });
812
+ try {
813
+ emitStructuredDataInternal(args);
814
+ scope.end();
815
+ } catch (error) {
816
+ scope.fail(error);
817
+ throw error;
818
+ }
819
+ };
717
820
 
718
821
  // src/tool.ts
719
822
  var import_node_async_hooks2 = require("async_hooks");
@@ -1191,6 +1294,11 @@ async function executeObservedTool(args) {
1191
1294
  }
1192
1295
  ctx.node.consumeExecution?.("maxToolCalls");
1193
1296
  observation.executed = true;
1297
+ const operationUsage = usage.start("sdk.tool", {
1298
+ calling_mode: args.callingMode,
1299
+ source: args.tool.source === "mcp" ? "mcp" : "native",
1300
+ executed: true
1301
+ });
1194
1302
  const started = performance.now();
1195
1303
  return withSafeTraceSpan(
1196
1304
  toolTrace(observation),
@@ -1240,9 +1348,13 @@ async function executeObservedTool(args) {
1240
1348
  scope.telemetry ?? args.tool.telemetry
1241
1349
  );
1242
1350
  }
1351
+ if (observation.outcome === "fault") operationUsage.fail(error);
1243
1352
  throw error;
1244
1353
  } finally {
1245
1354
  observation.durationMs = Math.max(0, performance.now() - started);
1355
+ operationUsage.end(
1356
+ observation.outcome === "fault" ? "failed" : observation.outcome ?? "suspended"
1357
+ );
1246
1358
  span.end?.({
1247
1359
  attributes: {
1248
1360
  ...observation,
@@ -1354,6 +1466,7 @@ var ownershipFrom = (observation) => ({
1354
1466
  // src/reason/engine.ts
1355
1467
  var import_node_crypto5 = require("crypto");
1356
1468
  var import_core4 = require("@kortyx/core");
1469
+ var import_prompts = require("@kortyx/prompts");
1357
1470
 
1358
1471
  // src/reason-engine.ts
1359
1472
  var import_node_crypto4 = require("crypto");
@@ -1398,7 +1511,7 @@ var resolveProviderOptions = (defaults, overrides) => {
1398
1511
  }
1399
1512
  return providerOptions;
1400
1513
  };
1401
- async function runReasonEngine(args) {
1514
+ async function runReasonEngineInternal(args) {
1402
1515
  let stream = args.stream ?? args.model.options?.streaming ?? true;
1403
1516
  const emit = args.emit ?? true;
1404
1517
  const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
@@ -1425,6 +1538,11 @@ async function runReasonEngine(args) {
1425
1538
  ...providerOptions !== void 0 ? { providerOptions } : {}
1426
1539
  });
1427
1540
  if (tools2?.length && !model.supportsToolStreaming) stream = false;
1541
+ usage.feature("sdk.model", {
1542
+ streaming: stream,
1543
+ tools: Boolean(tools2?.length),
1544
+ provider: args.model.provider.id
1545
+ });
1428
1546
  const messages = args.messages !== void 0 ? args.messages : [
1429
1547
  ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
1430
1548
  { role: "user", content: String(args.input ?? "") }
@@ -1504,7 +1622,7 @@ async function runReasonEngine(args) {
1504
1622
  let continuation;
1505
1623
  let final = "";
1506
1624
  let raw;
1507
- let usage;
1625
+ let usage2;
1508
1626
  let finishReason;
1509
1627
  let toolCalls;
1510
1628
  let providerMetadata;
@@ -1545,7 +1663,7 @@ async function runReasonEngine(args) {
1545
1663
  raw = chunk.raw;
1546
1664
  }
1547
1665
  if (chunk.usage !== void 0) {
1548
- usage = chunk.usage;
1666
+ usage2 = chunk.usage;
1549
1667
  }
1550
1668
  if (chunk.finishReason !== void 0) {
1551
1669
  finishReason = chunk.finishReason;
@@ -1583,7 +1701,7 @@ async function runReasonEngine(args) {
1583
1701
  }
1584
1702
  }
1585
1703
  (0, import_core3.throwIfExecutionAborted)(abortSignal);
1586
- assertModelFinish({ finishReason, usage });
1704
+ assertModelFinish({ finishReason, usage: usage2 });
1587
1705
  if (emit) {
1588
1706
  emitNodeEvent(args.emitEvent, args.nodeId, "text-end", {
1589
1707
  ...commonMeta
@@ -1593,7 +1711,7 @@ async function runReasonEngine(args) {
1593
1711
  text: final,
1594
1712
  ...continuation ? { continuation } : {},
1595
1713
  ...raw !== void 0 ? { raw } : {},
1596
- ...usage !== void 0 ? { usage } : {},
1714
+ ...usage2 !== void 0 ? { usage: usage2 } : {},
1597
1715
  ...finishReason !== void 0 ? { finishReason } : {},
1598
1716
  ...toolCalls !== void 0 ? { toolCalls } : {},
1599
1717
  ...providerMetadata !== void 0 ? { providerMetadata } : {},
@@ -1639,6 +1757,14 @@ async function runReasonEngine(args) {
1639
1757
  throw failure;
1640
1758
  }
1641
1759
  }
1760
+ function runReasonEngine(args) {
1761
+ return usage.measure("sdk.model", () => runReasonEngineInternal(args), {
1762
+ provider: args.model.provider.id,
1763
+ requested_streaming: args.stream ?? args.model.options?.streaming ?? true,
1764
+ tools: Boolean((args.tools ?? args.model.options?.tools)?.length),
1765
+ format: args.responseFormat?.type ?? args.model.options?.responseFormat?.type ?? "text"
1766
+ });
1767
+ }
1642
1768
 
1643
1769
  // src/reason/engine.ts
1644
1770
  var createRuntimeId2 = () => {
@@ -1657,6 +1783,19 @@ async function reasonEngine(args, meta, inputOverride) {
1657
1783
  );
1658
1784
  (0, import_core4.throwIfExecutionAborted)(abortSignal);
1659
1785
  ctx.node.consumeExecution?.("maxModelPasses");
1786
+ const prompt = args.telemetry?.prompt;
1787
+ const identity = prompt?.metadata;
1788
+ if ((0, import_prompts.isPromptUsageMetadata)(identity)) {
1789
+ const { dependencies, ...parent } = identity;
1790
+ ctx.node.prompts?.recordUsage({ ...parent, model: args.model.modelId });
1791
+ for (const dependency of dependencies ?? [])
1792
+ ctx.node.prompts?.recordUsage({
1793
+ ...dependency,
1794
+ environment: identity.environment,
1795
+ snapshotRevision: identity.snapshotRevision,
1796
+ model: args.model.modelId
1797
+ });
1798
+ }
1660
1799
  try {
1661
1800
  return await runReasonEngine({
1662
1801
  model: args.model,
@@ -1670,7 +1809,12 @@ async function reasonEngine(args, meta, inputOverride) {
1670
1809
  responseFormat: args.responseFormat,
1671
1810
  providerOptions: args.providerOptions,
1672
1811
  tools: args.tools,
1673
- messages: args.messages,
1812
+ messages: args.messages && inputOverride !== void 0 ? args.messages.map(
1813
+ (message, index) => index === args.messages.reduce(
1814
+ (last, item, position) => item.role === "user" ? position : last,
1815
+ -1
1816
+ ) ? { ...message, content: inputOverride } : message
1817
+ ) : args.messages,
1674
1818
  defaultTemperature: ctx.node.config?.model?.temperature,
1675
1819
  stream: args.stream,
1676
1820
  emit: args.emit,
@@ -1683,8 +1827,8 @@ async function reasonEngine(args, meta, inputOverride) {
1683
1827
  opId: meta.opId
1684
1828
  });
1685
1829
  } catch (error) {
1686
- const usage = error?.usage;
1687
- if (usage) accumulateTokenUsage(usage);
1830
+ const usage2 = error?.usage;
1831
+ if (usage2) accumulateTokenUsage(usage2);
1688
1832
  throw error;
1689
1833
  }
1690
1834
  }
@@ -2574,6 +2718,38 @@ var parseInterruptFirstPassResult = (args) => {
2574
2718
  }
2575
2719
  };
2576
2720
 
2721
+ // src/reason/prompt.ts
2722
+ var import_prompts2 = require("@kortyx/prompts");
2723
+ function normalizePromptReasonArgs(args) {
2724
+ if (!("prompt" in args)) return args;
2725
+ if (args.input !== void 0 || args.system !== void 0 || "messages" in args)
2726
+ throw new import_prompts2.PromptError(
2727
+ "PROMPT_REASON_CONFLICT",
2728
+ "Use either prompt or input/system/messages in useReason."
2729
+ );
2730
+ const { prompt, ...rest } = args;
2731
+ if (!(0, import_prompts2.isCompiledPrompt)(prompt))
2732
+ throw new import_prompts2.PromptError(
2733
+ "PROMPT_REASON_INVALID",
2734
+ "useReason requires a compiled prompt returned by usePrompt."
2735
+ );
2736
+ return {
2737
+ ...rest,
2738
+ input: prompt.format === "system-user" ? prompt.user ?? "" : prompt.messages.filter((message) => message.role === "user").at(-1)?.content ?? "",
2739
+ ...prompt.format === "system-user" ? { system: prompt.system } : { messages: structuredClone(prompt.messages) },
2740
+ telemetry: {
2741
+ ...args.telemetry,
2742
+ prompt: {
2743
+ name: prompt.ref.id,
2744
+ version: prompt.ref.version,
2745
+ type: prompt.format === "chat" ? "chat" : "text",
2746
+ source: prompt.ref.source,
2747
+ metadata: (0, import_prompts2.promptUsageMetadata)(prompt)
2748
+ }
2749
+ }
2750
+ };
2751
+ }
2752
+
2577
2753
  // src/reason/prompting.ts
2578
2754
  var import_zod4 = require("zod");
2579
2755
  var tryStringify2 = (value) => {
@@ -2823,19 +2999,34 @@ var normalizeToolResult = (toolCall, value) => {
2823
2999
  ...value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
2824
3000
  };
2825
3001
  };
2826
- var createInitialMessages = (args) => [
2827
- ...typeof args.system === "string" || args.interruptInstructions || args.outputInstructions ? [
2828
- {
2829
- role: "system",
2830
- content: [
2831
- args.system,
2832
- args.interruptInstructions,
2833
- args.outputInstructions
2834
- ].filter((value) => Boolean(value)).join("\n\n")
2835
- }
2836
- ] : [],
2837
- { role: "user", content: String(args.input ?? "") }
2838
- ];
3002
+ var createInitialMessages = (args) => {
3003
+ if (args.messages) {
3004
+ const ordered = args.messages.map((message) => ({ ...message }));
3005
+ const lastUser = ordered.reduce(
3006
+ (last, message, position) => message.role === "user" ? position : last,
3007
+ -1
3008
+ );
3009
+ if (args.inputOverride && lastUser >= 0)
3010
+ ordered[lastUser] = { ...ordered[lastUser], content: args.input };
3011
+ const instructions = [args.interruptInstructions, args.outputInstructions].filter(Boolean).join("\n\n");
3012
+ if (instructions)
3013
+ ordered.unshift({ role: "system", content: instructions });
3014
+ return ordered;
3015
+ }
3016
+ return [
3017
+ ...typeof args.system === "string" || args.interruptInstructions || args.outputInstructions ? [
3018
+ {
3019
+ role: "system",
3020
+ content: [
3021
+ args.system,
3022
+ args.interruptInstructions,
3023
+ args.outputInstructions
3024
+ ].filter((value) => Boolean(value)).join("\n\n")
3025
+ }
3026
+ ] : [],
3027
+ { role: "user", content: String(args.input ?? "") }
3028
+ ];
3029
+ };
2839
3030
  var runReasonToolLoop = async (args) => {
2840
3031
  const { useReasonArgs, id, traceSpan, checkpointKey } = args;
2841
3032
  const ctx = getHookContext();
@@ -2977,6 +3168,8 @@ var runReasonToolLoop = async (args) => {
2977
3168
  let completed = false;
2978
3169
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
2979
3170
  const messages = checkpoint?.messages ?? createInitialMessages({
3171
+ messages: useReasonArgs.messages,
3172
+ inputOverride: Boolean(useReasonArgs.outputSchema),
2980
3173
  system: useReasonArgs.system,
2981
3174
  input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input,
2982
3175
  ...interruptConfig ? {
@@ -3011,7 +3204,7 @@ var runReasonToolLoop = async (args) => {
3011
3204
  let finalRaw;
3012
3205
  let finalOutput;
3013
3206
  let aggregatedUsage = steps.reduce(
3014
- (usage, step) => mergeUsage(usage, step.usage),
3207
+ (usage2, step) => mergeUsage(usage2, step.usage),
3015
3208
  void 0
3016
3209
  );
3017
3210
  let finalFinishReason;
@@ -4114,7 +4307,10 @@ async function runReason(args) {
4114
4307
  }
4115
4308
  );
4116
4309
  }
4117
- async function useReason(args) {
4310
+ async function useReason(request) {
4311
+ const args = normalizePromptReasonArgs(
4312
+ request
4313
+ );
4118
4314
  try {
4119
4315
  return await runReason(args);
4120
4316
  } finally {
@@ -4419,6 +4615,38 @@ function reportError(error, options = {}) {
4419
4615
  }
4420
4616
  }
4421
4617
 
4618
+ // src/use-prompt.ts
4619
+ var import_prompts3 = require("@kortyx/prompts");
4620
+ async function usePrompt(reference, options) {
4621
+ const ctx = getHookContext();
4622
+ if (!ctx.node.prompts)
4623
+ throw new import_prompts3.PromptError(
4624
+ "PROMPT_SOURCE_NOT_CONFIGURED",
4625
+ "Configure createAgent({ prompts: createPrompts(...) }) before calling usePrompt."
4626
+ );
4627
+ const stored = ctx.workflowState.__promptSnapshot;
4628
+ const snapshot = await ctx.node.prompts.snapshot(stored);
4629
+ if (!stored) {
4630
+ ctx.workflowState.__promptSnapshot = snapshot;
4631
+ ctx.stateDirty = true;
4632
+ }
4633
+ const key = `__promptPin:${reference.id}:${options.version ?? "assignment"}`;
4634
+ const pin = options.version !== void 0 ? await ctx.node.prompts.pin(
4635
+ reference.id,
4636
+ options.version,
4637
+ ctx.workflowState[key]
4638
+ ) : void 0;
4639
+ if (pin && !ctx.workflowState[key]) {
4640
+ ctx.workflowState[key] = pin;
4641
+ ctx.stateDirty = true;
4642
+ }
4643
+ return ctx.node.prompts.resolve(reference, {
4644
+ ...options,
4645
+ stored: snapshot,
4646
+ ...pin ? { pin } : {}
4647
+ });
4648
+ }
4649
+
4422
4650
  // src/workflow.ts
4423
4651
  var import_node_crypto6 = require("crypto");
4424
4652
  var import_core8 = require("@kortyx/core");
@@ -4574,14 +4802,14 @@ async function runWorkflowCall(args, task) {
4574
4802
  ...content
4575
4803
  });
4576
4804
  };
4577
- const recordUsage = (usage) => {
4578
- if (!usage) return;
4805
+ const recordUsage = (usage2) => {
4806
+ if (!usage2) return;
4579
4807
  accumulateTokenUsage({
4580
- input: usage.input - (record.usage?.input ?? 0),
4581
- output: usage.output - (record.usage?.output ?? 0),
4582
- total: usage.total - (record.usage?.total ?? 0)
4808
+ input: usage2.input - (record.usage?.input ?? 0),
4809
+ output: usage2.output - (record.usage?.output ?? 0),
4810
+ total: usage2.total - (record.usage?.total ?? 0)
4583
4811
  });
4584
- record.usage = usage;
4812
+ record.usage = usage2;
4585
4813
  };
4586
4814
  const cached = record.status === "completed";
4587
4815
  try {
@@ -4702,6 +4930,7 @@ async function runWorkflowCall(args, task) {
4702
4930
  useAbortSignal,
4703
4931
  useInterrupt,
4704
4932
  useNodeState,
4933
+ usePrompt,
4705
4934
  useReason,
4706
4935
  useRuntimeContext,
4707
4936
  useStructuredData,