@namzu/sdk 19.0.0 → 20.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/CHANGELOG.md +84 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +1 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/runAgent.d.ts +20 -0
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +2 -1
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +27 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +28 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +44 -9
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +1 -0
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/store/run/disk.d.ts.map +1 -1
- package/dist/store/run/disk.js +6 -0
- package/dist/store/run/disk.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +20 -5
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +19 -2
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/agent/base.d.ts +20 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/plugin/index.d.ts +30 -1
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +1 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +1 -0
- package/src/agents/runAgent.ts +24 -1
- package/src/manager/run/persistence.ts +28 -0
- package/src/runtime/query/executor.ts +59 -11
- package/src/runtime/query/plugin-hooks.ts +1 -0
- package/src/store/run/disk.ts +6 -0
- package/src/tools/coordinator/agent.ts +20 -5
- package/src/tools/coordinator/index.ts +22 -2
- package/src/types/agent/base.ts +20 -0
- package/src/types/plugin/index.ts +27 -1
|
@@ -144,6 +144,20 @@ export interface ToolExecutorConfig {
|
|
|
144
144
|
repairToolCall?: RepairToolCall
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/**
|
|
148
|
+
* What a `post_tool_use` hook decided to show the model instead.
|
|
149
|
+
*
|
|
150
|
+
* `isError` is the field this type exists for. The override used to be a bare
|
|
151
|
+
* string, so the executor had no way to tell "the call failed" from "the call
|
|
152
|
+
* succeeded and the model may not see all of it" — and it assumed the first,
|
|
153
|
+
* which turned every redaction into a reported tool failure.
|
|
154
|
+
*/
|
|
155
|
+
interface PostToolOverride {
|
|
156
|
+
readonly output: string
|
|
157
|
+
readonly isError: boolean
|
|
158
|
+
readonly content?: ToolResultContent
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
type PreToolHookOutcome =
|
|
148
162
|
| { kind: 'continue'; input: unknown }
|
|
149
163
|
| { kind: 'skip'; input: unknown; output: string }
|
|
@@ -708,10 +722,14 @@ export class ToolExecutor {
|
|
|
708
722
|
|
|
709
723
|
const postOverride = post.override
|
|
710
724
|
if (postOverride !== null) {
|
|
711
|
-
output = postOverride
|
|
725
|
+
output = postOverride.output
|
|
712
726
|
}
|
|
713
727
|
|
|
714
|
-
|
|
728
|
+
// A failed call, or an override that says the call failed. A `replace`
|
|
729
|
+
// says the opposite, and reading it as a failure is what made redaction
|
|
730
|
+
// unusable: the model was told a successful call had gone wrong, and
|
|
731
|
+
// routed around it.
|
|
732
|
+
const effectiveIsError = !result.success || (postOverride?.isError ?? false)
|
|
715
733
|
|
|
716
734
|
if (this.workingStateManager) {
|
|
717
735
|
extractFromToolResult(this.workingStateManager, toolName, output, effectiveIsError)
|
|
@@ -756,17 +774,31 @@ export class ToolExecutor {
|
|
|
756
774
|
...(budgeted.spillPath ? { outputSpillPath: budgeted.spillPath } : {}),
|
|
757
775
|
})
|
|
758
776
|
|
|
777
|
+
const resolveContent = (): { content?: ToolResultContent } => {
|
|
778
|
+
if (postOverride?.content !== undefined) {
|
|
779
|
+
return { content: this.budgetContent(postOverride.content, toolName) }
|
|
780
|
+
}
|
|
781
|
+
if (postOverride?.isError) return {}
|
|
782
|
+
if (budgeted.truncated || result.content === undefined) return {}
|
|
783
|
+
return { content: this.budgetContent(result.content, toolName) }
|
|
784
|
+
}
|
|
785
|
+
|
|
759
786
|
return {
|
|
760
787
|
toolCallId: toolCall.id,
|
|
761
788
|
toolName,
|
|
762
789
|
output,
|
|
763
790
|
isError: effectiveIsError,
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
791
|
+
// Rich content follows the override's own decision.
|
|
792
|
+
//
|
|
793
|
+
// An ERROR override drops it: the payload is no longer the tool's,
|
|
794
|
+
// and shipping an image beside a failure message describes something
|
|
795
|
+
// the model was just told did not happen. A spilled preview drops it
|
|
796
|
+
// for the same reason.
|
|
797
|
+
//
|
|
798
|
+
// A REPLACE keeps it, because the common case is redacting text from
|
|
799
|
+
// a result whose image is unaffected — and a hook that needs it gone
|
|
800
|
+
// says so with `content`, which wins over both.
|
|
801
|
+
...resolveContent(),
|
|
770
802
|
}
|
|
771
803
|
}
|
|
772
804
|
|
|
@@ -919,6 +951,11 @@ export class ToolExecutor {
|
|
|
919
951
|
output: `Error: ${result.message}`,
|
|
920
952
|
}
|
|
921
953
|
case 'retry':
|
|
954
|
+
// There is no result to replace yet. Rejecting loudly beats
|
|
955
|
+
// silently ignoring it: a hook author who returned this here
|
|
956
|
+
// meant to redact something and would otherwise watch the secret
|
|
957
|
+
// go through.
|
|
958
|
+
case 'replace':
|
|
922
959
|
throw new Error(
|
|
923
960
|
`Plugin hook pre_tool_use returned unsupported action '${result.action}' for tool ${toolName}`,
|
|
924
961
|
)
|
|
@@ -1120,21 +1157,32 @@ export class ToolExecutor {
|
|
|
1120
1157
|
toolName: string,
|
|
1121
1158
|
input: unknown,
|
|
1122
1159
|
toolResult: ToolResult,
|
|
1123
|
-
): Promise<{ override:
|
|
1160
|
+
): Promise<{ override: PostToolOverride | null; retry: boolean }> {
|
|
1124
1161
|
if (!this.config.pluginManager) return { override: null, retry: false }
|
|
1125
1162
|
const results = await this.config.pluginManager.executeHooks(
|
|
1126
1163
|
'post_tool_use',
|
|
1127
1164
|
{ runId: this.config.runId, toolName, toolInput: input, toolResult },
|
|
1128
1165
|
this.emitEvent,
|
|
1129
1166
|
)
|
|
1130
|
-
let override:
|
|
1167
|
+
let override: PostToolOverride | null = null
|
|
1131
1168
|
let retry = false
|
|
1132
1169
|
for (const result of results) {
|
|
1133
1170
|
switch (result.action) {
|
|
1134
1171
|
case 'continue':
|
|
1135
1172
|
continue
|
|
1136
1173
|
case 'error':
|
|
1137
|
-
override = `Error: ${result.message}
|
|
1174
|
+
override = { output: `Error: ${result.message}`, isError: true }
|
|
1175
|
+
continue
|
|
1176
|
+
// A redaction, not a failure. The call stood; the model is shown
|
|
1177
|
+
// less of it. Rich content survives unless the hook replaced it —
|
|
1178
|
+
// see the variant's own documentation for why that default, and
|
|
1179
|
+
// for what a hook redacting a secret in an image has to do.
|
|
1180
|
+
case 'replace':
|
|
1181
|
+
override = {
|
|
1182
|
+
output: result.output,
|
|
1183
|
+
isError: false,
|
|
1184
|
+
...(result.content !== undefined ? { content: result.content } : {}),
|
|
1185
|
+
}
|
|
1138
1186
|
continue
|
|
1139
1187
|
// `retry` was a declared variant with no implementation: every
|
|
1140
1188
|
// site that consumed it threw. Here it finally means something
|
package/src/store/run/disk.ts
CHANGED
|
@@ -136,6 +136,12 @@ export class RunDiskStore {
|
|
|
136
136
|
messageCount: run.messages.length,
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// The schema-validated answer belongs in the durable record for the same
|
|
140
|
+
// reason `result` does: a run reloaded by id that has lost its answer has
|
|
141
|
+
// lost the thing it was run for. Written only when present, so a run that
|
|
142
|
+
// asked for no schema carries no key rather than an explicit `undefined`.
|
|
143
|
+
if (run.structuredOutput !== undefined) meta.structuredOutput = run.structuredOutput
|
|
144
|
+
|
|
139
145
|
if (run.parentRunId) meta.parentRunId = run.parentRunId
|
|
140
146
|
if (run.depth !== undefined && run.depth > 0) meta.depth = run.depth
|
|
141
147
|
|
|
@@ -175,12 +175,27 @@ export function buildAgentTool(opts: AgentToolOptions): ToolDefinition {
|
|
|
175
175
|
// the other one.
|
|
176
176
|
const succeeded = taskSucceeded(completed)
|
|
177
177
|
|
|
178
|
+
// A schema-configured child answers with an OBJECT, and this used to
|
|
179
|
+
// hand the parent model the child's prose instead — so a supervisor
|
|
180
|
+
// fanning out to five specialists got five strings and had to
|
|
181
|
+
// re-parse what it had just caused to be serialized.
|
|
182
|
+
//
|
|
183
|
+
// `structuredOutput` wins over `result` when present, and reading it
|
|
184
|
+
// first is what makes that true. They agree by construction anyway:
|
|
185
|
+
// `setStructuredOutput` serializes the value into `result`, so this
|
|
186
|
+
// preference is about which field is authoritative rather than about
|
|
187
|
+
// which string is produced.
|
|
188
|
+
const structured = completed.result?.structuredOutput
|
|
178
189
|
const resultText =
|
|
179
|
-
|
|
180
|
-
?
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
structured !== undefined
|
|
191
|
+
? typeof structured === 'string'
|
|
192
|
+
? structured
|
|
193
|
+
: JSON.stringify(structured)
|
|
194
|
+
: typeof completed.result?.result === 'string'
|
|
195
|
+
? completed.result.result
|
|
196
|
+
: completed.result?.result !== undefined
|
|
197
|
+
? JSON.stringify(completed.result.result)
|
|
198
|
+
: ''
|
|
184
199
|
|
|
185
200
|
if (!succeeded) {
|
|
186
201
|
const detail =
|
|
@@ -288,6 +288,26 @@ function readPositiveIntEnv(key: string, fallback: number): number {
|
|
|
288
288
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* The answer a delegated child produced, as the string a parent model reads.
|
|
293
|
+
*
|
|
294
|
+
* One function because two delegation surfaces ask the same question, and the
|
|
295
|
+
* comment on the other one records what happens when a rule lives at one site
|
|
296
|
+
* only: create_task shipped without the success check that agent already had.
|
|
297
|
+
*
|
|
298
|
+
* A schema-configured child answers with an object. Reading structuredOutput
|
|
299
|
+
* first is what stops a supervisor receiving prose it then has to re-parse.
|
|
300
|
+
*/
|
|
301
|
+
function delegatedAnswer(
|
|
302
|
+
result: { structuredOutput?: unknown; result?: string } | undefined,
|
|
303
|
+
): string | undefined {
|
|
304
|
+
const structured = result?.structuredOutput
|
|
305
|
+
if (structured !== undefined) {
|
|
306
|
+
return typeof structured === 'string' ? structured : JSON.stringify(structured)
|
|
307
|
+
}
|
|
308
|
+
return result?.result
|
|
309
|
+
}
|
|
310
|
+
|
|
291
311
|
export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefinition[] {
|
|
292
312
|
const {
|
|
293
313
|
gateway,
|
|
@@ -614,7 +634,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
614
634
|
// the work had been done.
|
|
615
635
|
const success = taskSucceeded(completed)
|
|
616
636
|
const resultText =
|
|
617
|
-
completed.result
|
|
637
|
+
delegatedAnswer(completed.result) ??
|
|
618
638
|
completed.result?.lastError ??
|
|
619
639
|
`Task finished with state: ${failureLabel(completed)}`
|
|
620
640
|
|
|
@@ -744,7 +764,7 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
|
|
|
744
764
|
|
|
745
765
|
const success = completed.state === 'completed'
|
|
746
766
|
const resultText =
|
|
747
|
-
completed.result
|
|
767
|
+
delegatedAnswer(completed.result) ??
|
|
748
768
|
completed.result?.lastError ??
|
|
749
769
|
`Task finished with state: ${completed.state}`
|
|
750
770
|
return {
|
package/src/types/agent/base.ts
CHANGED
|
@@ -186,6 +186,26 @@ export interface BaseAgentResult {
|
|
|
186
186
|
durationMs: number
|
|
187
187
|
messages: Message[]
|
|
188
188
|
result?: string
|
|
189
|
+
/**
|
|
190
|
+
* The schema-validated answer, when the run was configured to produce one.
|
|
191
|
+
*
|
|
192
|
+
* `Run.structuredOutput` has carried this all along and every ergonomic
|
|
193
|
+
* boundary above it dropped the value three lines from its caller: an
|
|
194
|
+
* archetype's result literal did not copy it, `runAgent` did not even
|
|
195
|
+
* forward the config that produces it, and both delegation tools handed a
|
|
196
|
+
* parent the child's prose. So a supervisor fanning out to five
|
|
197
|
+
* schema-configured specialists received five strings and had to make the
|
|
198
|
+
* model re-parse what it had just caused to be serialized.
|
|
199
|
+
*
|
|
200
|
+
* `unknown` rather than a generic, deliberately. The schema lives on the
|
|
201
|
+
* run's config and a result type parameter would have to be threaded
|
|
202
|
+
* through every archetype, both delegation tools and the task record to
|
|
203
|
+
* reach here — and at the delegation boundary the parent does not hold the
|
|
204
|
+
* child's schema anyway, so the parameter would be `unknown` again at the
|
|
205
|
+
* only place it was wanted. Narrow it at the call site with the schema you
|
|
206
|
+
* already have.
|
|
207
|
+
*/
|
|
208
|
+
structuredOutput?: unknown
|
|
189
209
|
lastError?: string
|
|
190
210
|
}
|
|
191
211
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
PLUGIN_NAME_MAX_LENGTH,
|
|
10
10
|
} from '../../constants/plugin/index.js'
|
|
11
11
|
import type { PluginId, RunId } from '../ids/index.js'
|
|
12
|
-
import type { Message } from '../message/index.js'
|
|
12
|
+
import type { Message, ToolResultContent } from '../message/index.js'
|
|
13
13
|
import type { ToolResult } from '../tool/index.js'
|
|
14
14
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -191,6 +191,31 @@ export type PluginHookResult =
|
|
|
191
191
|
| { action: 'modify'; input: unknown }
|
|
192
192
|
| { action: 'error'; message: string }
|
|
193
193
|
| { action: 'retry' }
|
|
194
|
+
/**
|
|
195
|
+
* Replace what the model sees, WITHOUT reporting the call as failed.
|
|
196
|
+
*
|
|
197
|
+
* The substitution seam already existed and was typed as a failure channel:
|
|
198
|
+
* the only way a `post_tool_use` hook could change the output was
|
|
199
|
+
* `action: 'error'`, which prefixes `Error: ` and sets the error flag. So
|
|
200
|
+
* redacting a credential out of a successful result was delivered to the
|
|
201
|
+
* model as a tool failure, and the model routed around a call that had
|
|
202
|
+
* worked — retrying it, or reporting to the user that it had failed.
|
|
203
|
+
*
|
|
204
|
+
* That is the difference this variant exists for. `error` says the call went
|
|
205
|
+
* wrong; this says the call went right and the model may not see all of it.
|
|
206
|
+
*
|
|
207
|
+
* `modify` is not this. It carries `input` and belongs to the pre-call
|
|
208
|
+
* hooks, which is why `post_tool_use` rejects it — a result is not an input,
|
|
209
|
+
* and reusing the variant would have made one action mean two things
|
|
210
|
+
* depending on where it was returned.
|
|
211
|
+
*
|
|
212
|
+
* Rich content blocks SURVIVE a replace unless `content` is given, because
|
|
213
|
+
* the common case is redacting text from a result whose image or resource
|
|
214
|
+
* is unaffected. A hook that needs to drop them passes `content: []`, and a
|
|
215
|
+
* hook redacting a secret that also appears in an image must — this variant
|
|
216
|
+
* cannot inspect what it is preserving.
|
|
217
|
+
*/
|
|
218
|
+
| { action: 'replace'; output: string; content?: ToolResultContent }
|
|
194
219
|
|
|
195
220
|
export function assertPluginHookResult(result: PluginHookResult): asserts result {
|
|
196
221
|
const action = result.action
|
|
@@ -200,6 +225,7 @@ export function assertPluginHookResult(result: PluginHookResult): asserts result
|
|
|
200
225
|
case 'modify':
|
|
201
226
|
case 'error':
|
|
202
227
|
case 'retry':
|
|
228
|
+
case 'replace':
|
|
203
229
|
break
|
|
204
230
|
default: {
|
|
205
231
|
const _exhaustive: never = action
|