@noetic-tools/core 5.4.0 → 5.4.1
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/builders/acp-agent-tool.d.ts +67 -0
- package/dist/builders/acp-agent-tool.js +87 -0
- package/dist/builders/dynamic-workflow.js +2 -2
- package/dist/builders/step-builders.d.ts +42 -42
- package/dist/builders/step-builders.js +38 -62
- package/dist/builders/workflow-hydrator.d.ts +8 -4
- package/dist/builders/workflow-hydrator.js +16 -24
- package/dist/builders/workflow-step.d.ts +3 -3
- package/dist/builders/workflow-step.js +1 -1
- package/dist/harness/agent-harness.d.ts +36 -7
- package/dist/harness/agent-harness.js +49 -6
- package/dist/harness/deps/types.d.ts +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +3 -3
- package/dist/interpreter/acp-events.d.ts +67 -0
- package/dist/interpreter/acp-events.js +256 -0
- package/dist/interpreter/acp-history.d.ts +22 -0
- package/dist/interpreter/acp-history.js +58 -0
- package/dist/interpreter/collect-tools.js +3 -6
- package/dist/interpreter/execute-acp-agent.d.ts +10 -0
- package/dist/interpreter/execute-acp-agent.js +505 -0
- package/dist/interpreter/execute.js +4 -11
- package/dist/interpreter/structured-output.d.ts +1 -1
- package/dist/interpreter/structured-output.js +1 -1
- package/dist/portable/builders.d.ts +2 -0
- package/dist/portable/builders.js +3 -1
- package/dist/runtime/acp-session-store.d.ts +75 -0
- package/dist/runtime/acp-session-store.js +0 -0
- package/dist/schemas/workflow.d.ts +1 -1
- package/package.json +3 -3
- package/schema/noetic-workflow.schema.json +215 -249
- package/dist/interpreter/execute-sub-harness.d.ts +0 -8
- package/dist/interpreter/execute-sub-harness.js +0 -222
- package/dist/interpreter/sub-harness-events.d.ts +0 -48
- package/dist/interpreter/sub-harness-events.js +0 -208
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SubHarness step handler: drives an external coding-agent harness
|
|
3
|
-
* (Claude Code, Codex, opencode, pi) for one agentic turn and folds its output
|
|
4
|
-
* back into the Noetic execution context — the harness analogue of
|
|
5
|
-
* `executeCallModel`.
|
|
6
|
-
*/
|
|
7
|
-
import { createMessage, extractAssistantText, frameworkCast, NoeticConfigError, NoeticErrorImpl, } from '@noetic-tools/types';
|
|
8
|
-
import { resolveLazy } from './execute-action.js';
|
|
9
|
-
import { trackUsage } from './message-helpers.js';
|
|
10
|
-
import { parseStructuredOutput } from './structured-output.js';
|
|
11
|
-
import { SubHarnessEventBridge } from './sub-harness-events.js';
|
|
12
|
-
import { isContextImpl, isFunctionCall, isMutableContext } from './typeguards.js';
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region Helpers
|
|
15
|
-
function sessionStore(ctx) {
|
|
16
|
-
return frameworkCast(ctx.harness).subHarnessSessions;
|
|
17
|
-
}
|
|
18
|
-
async function resolveSubHarness(step, ctx) {
|
|
19
|
-
const resolved = await resolveLazy(step.harness, ctx);
|
|
20
|
-
if (!resolved) {
|
|
21
|
-
throw new NoeticConfigError({
|
|
22
|
-
code: 'MISSING_SUB_HARNESS',
|
|
23
|
-
message: `step.${step.kind}(${JSON.stringify(step.id)}) resolved no harness adapter.`,
|
|
24
|
-
hint: 'Pass a harness factory result, e.g. harness: claudeCode({ model }).',
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (resolved.harnessId !== step.kind) {
|
|
28
|
-
throw new NoeticConfigError({
|
|
29
|
-
code: 'SUB_HARNESS_KIND_MISMATCH',
|
|
30
|
-
message: `step.${step.kind}(${JSON.stringify(step.id)}) was given a '${resolved.harnessId}' harness.`,
|
|
31
|
-
hint: `Use the matching builder for this adapter, e.g. step.${resolved.harnessId}({ ... }).`,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return resolved;
|
|
35
|
-
}
|
|
36
|
-
function resolveTurnText(resolvedPrompt, input) {
|
|
37
|
-
if (resolvedPrompt && resolvedPrompt.length > 0) {
|
|
38
|
-
return resolvedPrompt;
|
|
39
|
-
}
|
|
40
|
-
return typeof input === 'string' ? input : '';
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Abort signal of the executing context, when it is a framework context. Handed
|
|
44
|
-
* to the adapter so `ctx.abort()` / `harness.cancel()` stops the coding agent's
|
|
45
|
-
* in-flight turn instead of leaving a sub-process running until it finishes.
|
|
46
|
-
*/
|
|
47
|
-
function abortSignalOf(ctx) {
|
|
48
|
-
return isContextImpl(ctx) ? ctx.abortSignal : undefined;
|
|
49
|
-
}
|
|
50
|
-
function buildRunContext(ctx) {
|
|
51
|
-
return {
|
|
52
|
-
cwd: ctx.cwdState.cwd,
|
|
53
|
-
fs: ctx.fs,
|
|
54
|
-
shell: ctx.shell,
|
|
55
|
-
subprocess: ctx.subprocess,
|
|
56
|
-
threadId: ctx.threadId,
|
|
57
|
-
signal: abortSignalOf(ctx),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function toLlmResponse(result) {
|
|
61
|
-
const usage = result.usage;
|
|
62
|
-
return {
|
|
63
|
-
items: result.items,
|
|
64
|
-
usage: {
|
|
65
|
-
inputTokens: usage?.input ?? 0,
|
|
66
|
-
outputTokens: usage?.output ?? 0,
|
|
67
|
-
cachedTokens: usage?.cached,
|
|
68
|
-
},
|
|
69
|
-
cost: result.cost,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function applyTurnResult(ctx, result) {
|
|
73
|
-
const toolCalls = [];
|
|
74
|
-
for (const item of result.items) {
|
|
75
|
-
ctx.itemLog.append(item);
|
|
76
|
-
if (isFunctionCall(item)) {
|
|
77
|
-
toolCalls.push(item);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const llmResponse = toLlmResponse(result);
|
|
81
|
-
trackUsage(ctx, llmResponse);
|
|
82
|
-
const meta = {
|
|
83
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
84
|
-
usage: result.usage ? llmResponse.usage : undefined,
|
|
85
|
-
cost: result.cost,
|
|
86
|
-
responseItems: result.items,
|
|
87
|
-
};
|
|
88
|
-
if (isMutableContext(ctx)) {
|
|
89
|
-
ctx.lastStepMeta = meta;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function teardownSession(session, mode) {
|
|
93
|
-
if (mode === 'detach' && session.doDetach) {
|
|
94
|
-
await session.doDetach();
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (mode === 'destroy' && session.doDestroy) {
|
|
98
|
-
await session.doDestroy();
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
await session.doStop();
|
|
102
|
-
}
|
|
103
|
-
async function startOrReuseSession(step, harness, ctx, baseCtx, history) {
|
|
104
|
-
const reuseKey = step.session?.reuse;
|
|
105
|
-
const store = sessionStore(baseCtx);
|
|
106
|
-
if (reuseKey) {
|
|
107
|
-
const existing = store.get(reuseKey);
|
|
108
|
-
if (existing) {
|
|
109
|
-
return {
|
|
110
|
-
session: existing,
|
|
111
|
-
reuseKey,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const session = await harness.doStart({
|
|
116
|
-
settings: step.settings,
|
|
117
|
-
instructions: await resolveLazy(step.instructions, ctx),
|
|
118
|
-
history,
|
|
119
|
-
ctx: buildRunContext(baseCtx),
|
|
120
|
-
signal: abortSignalOf(baseCtx),
|
|
121
|
-
});
|
|
122
|
-
if (reuseKey) {
|
|
123
|
-
store.set(reuseKey, session);
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
session,
|
|
127
|
-
reuseKey,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Tear the session down per policy after a successful turn. Reused sessions
|
|
132
|
-
* stay alive by default; an explicit `onComplete` overrides that.
|
|
133
|
-
*/
|
|
134
|
-
async function finalizeSession(resolution, policy, baseCtx) {
|
|
135
|
-
const { session, reuseKey } = resolution;
|
|
136
|
-
if (!reuseKey) {
|
|
137
|
-
await teardownSession(session, policy?.onComplete ?? 'stop');
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const mode = policy?.onComplete;
|
|
141
|
-
if (mode === 'stop' || mode === 'destroy') {
|
|
142
|
-
await teardownSession(session, mode);
|
|
143
|
-
sessionStore(baseCtx).delete(reuseKey);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (mode === 'detach') {
|
|
147
|
-
await teardownSession(session, 'detach');
|
|
148
|
-
}
|
|
149
|
-
// Default for a reused session: keep it alive in the store.
|
|
150
|
-
}
|
|
151
|
-
//#endregion
|
|
152
|
-
//#region Public API
|
|
153
|
-
export async function executeSubHarness(step, input, ctx) {
|
|
154
|
-
const baseCtx = frameworkCast(ctx);
|
|
155
|
-
const harness = await resolveSubHarness(step, ctx);
|
|
156
|
-
const resolvedPrompt = await resolveLazy(step.prompt, ctx);
|
|
157
|
-
const turnText = resolveTurnText(resolvedPrompt, input);
|
|
158
|
-
if (turnText.trim() === '') {
|
|
159
|
-
throw new NoeticConfigError({
|
|
160
|
-
code: 'MISSING_PROMPT',
|
|
161
|
-
message: `step.${step.kind}(${JSON.stringify(step.id)}) resolved an empty prompt.`,
|
|
162
|
-
hint: 'Provide a non-empty `prompt`, or pass a string input to the step.',
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
// Capture the conversation so far (from earlier LLM/sub-harness steps and
|
|
166
|
-
// turns) BEFORE appending this turn's prompt, so a fresh session is seeded
|
|
167
|
-
// with full context of the conversation.
|
|
168
|
-
const priorHistory = [
|
|
169
|
-
...baseCtx.itemLog.items,
|
|
170
|
-
];
|
|
171
|
-
baseCtx.itemLog.append(createMessage(turnText, 'user'));
|
|
172
|
-
const resolution = await startOrReuseSession(step, harness, ctx, baseCtx, priorHistory);
|
|
173
|
-
const bridge = new SubHarnessEventBridge(step, baseCtx);
|
|
174
|
-
bridge.begin();
|
|
175
|
-
let result;
|
|
176
|
-
try {
|
|
177
|
-
result = await resolution.session.doPromptTurn({
|
|
178
|
-
prompt: turnText,
|
|
179
|
-
emit: (part) => bridge.forward(part),
|
|
180
|
-
// Per-turn signal, so a session reused across turns is cancelled by the
|
|
181
|
-
// context running the CURRENT turn rather than the one that started it.
|
|
182
|
-
signal: abortSignalOf(baseCtx),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
// Best-effort teardown of a fresh session before surfacing the failure;
|
|
187
|
-
// reused sessions are left intact for a later step to retry against.
|
|
188
|
-
if (!resolution.reuseKey) {
|
|
189
|
-
await teardownSession(resolution.session, 'destroy').catch(() => undefined);
|
|
190
|
-
}
|
|
191
|
-
if (e instanceof NoeticErrorImpl) {
|
|
192
|
-
throw e;
|
|
193
|
-
}
|
|
194
|
-
// An adapter interrupted by the abort signal rejects with whatever its SDK
|
|
195
|
-
// throws; the context is the authority on why the turn ended.
|
|
196
|
-
if (baseCtx.aborted) {
|
|
197
|
-
throw new NoeticErrorImpl({
|
|
198
|
-
kind: 'cancelled',
|
|
199
|
-
reason: baseCtx.abortReason ?? 'context aborted',
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
throw new NoeticErrorImpl({
|
|
203
|
-
kind: 'step_failed',
|
|
204
|
-
stepId: step.id,
|
|
205
|
-
cause: e instanceof Error ? e : new Error(String(e)),
|
|
206
|
-
retriesExhausted: false,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
bridge.finalize(result);
|
|
210
|
-
applyTurnResult(baseCtx, result);
|
|
211
|
-
await finalizeSession(resolution, step.session, baseCtx);
|
|
212
|
-
const lastText = result.text.length > 0 ? result.text : extractAssistantText(result.items);
|
|
213
|
-
if (step.output) {
|
|
214
|
-
return parseStructuredOutput({
|
|
215
|
-
schema: step.output,
|
|
216
|
-
rawText: lastText,
|
|
217
|
-
stepId: step.id,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return frameworkCast(lastText);
|
|
221
|
-
}
|
|
222
|
-
//#endregion
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maps a sub-harness turn's output onto the harness's observable event surface.
|
|
3
|
-
*
|
|
4
|
-
* A sub-harness adapter emits {@link SubHarnessStreamPart}s; this bridge
|
|
5
|
-
* translates each one into the same `source: 'sdk'` broadcaster events the
|
|
6
|
-
* model-call path emits, so a coding agent's text, reasoning, and tool calls
|
|
7
|
-
* flow through `getTextStream()` / `getReasoningStream()` / `getItemStream()` /
|
|
8
|
-
* `getFullStream()` exactly like an LLM step's output. It also re-emits the raw
|
|
9
|
-
* part as a `sub_harness_event` framework event for harness-native consumers.
|
|
10
|
-
*
|
|
11
|
-
* `finalize()` guarantees a turn always emits its output: if an adapter returns
|
|
12
|
-
* a result without streaming any text/tool parts, the result's content is
|
|
13
|
-
* synthesized into events.
|
|
14
|
-
*/
|
|
15
|
-
import type { Context, ContextData, StepSubHarness, SubHarnessStreamPart, SubHarnessTurnResult } from '@noetic-tools/types';
|
|
16
|
-
export declare class SubHarnessEventBridge {
|
|
17
|
-
private readonly broadcaster?;
|
|
18
|
-
private readonly agentName;
|
|
19
|
-
private readonly stepId;
|
|
20
|
-
private readonly stepKind;
|
|
21
|
-
private readonly emitOption;
|
|
22
|
-
private started;
|
|
23
|
-
private completed;
|
|
24
|
-
private nextOutputIndex;
|
|
25
|
-
private messageOutputIndex;
|
|
26
|
-
private sawText;
|
|
27
|
-
private sawToolCall;
|
|
28
|
-
constructor(step: Pick<StepSubHarness, 'id' | 'kind' | 'emit'>, ctx: Context<ContextData>);
|
|
29
|
-
/**
|
|
30
|
-
* Open the turn on the event surface. Emits `response.created` so every turn
|
|
31
|
-
* always brackets with a lifecycle marker — even a custom adapter that
|
|
32
|
-
* streams nothing still emits output.
|
|
33
|
-
*/
|
|
34
|
-
begin(): void;
|
|
35
|
-
/** Forward one stream part: a structured framework event + the mapped sdk events. */
|
|
36
|
-
forward(part: SubHarnessStreamPart): void;
|
|
37
|
-
/**
|
|
38
|
-
* Ensure the turn's output reached the event surface. For adapters that
|
|
39
|
-
* return a result without streaming, synthesize events from the result.
|
|
40
|
-
*/
|
|
41
|
-
finalize(result: SubHarnessTurnResult): void;
|
|
42
|
-
private sdk;
|
|
43
|
-
private start;
|
|
44
|
-
private openMessage;
|
|
45
|
-
private closeMessage;
|
|
46
|
-
private emitToolCall;
|
|
47
|
-
private translate;
|
|
48
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maps a sub-harness turn's output onto the harness's observable event surface.
|
|
3
|
-
*
|
|
4
|
-
* A sub-harness adapter emits {@link SubHarnessStreamPart}s; this bridge
|
|
5
|
-
* translates each one into the same `source: 'sdk'` broadcaster events the
|
|
6
|
-
* model-call path emits, so a coding agent's text, reasoning, and tool calls
|
|
7
|
-
* flow through `getTextStream()` / `getReasoningStream()` / `getItemStream()` /
|
|
8
|
-
* `getFullStream()` exactly like an LLM step's output. It also re-emits the raw
|
|
9
|
-
* part as a `sub_harness_event` framework event for harness-native consumers.
|
|
10
|
-
*
|
|
11
|
-
* `finalize()` guarantees a turn always emits its output: if an adapter returns
|
|
12
|
-
* a result without streaming any text/tool parts, the result's content is
|
|
13
|
-
* synthesized into events.
|
|
14
|
-
*/
|
|
15
|
-
import { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
|
|
16
|
-
import { isFunctionCall } from './typeguards.js';
|
|
17
|
-
export class SubHarnessEventBridge {
|
|
18
|
-
broadcaster;
|
|
19
|
-
agentName;
|
|
20
|
-
stepId;
|
|
21
|
-
stepKind;
|
|
22
|
-
emitOption;
|
|
23
|
-
started = false;
|
|
24
|
-
completed = false;
|
|
25
|
-
nextOutputIndex = 0;
|
|
26
|
-
messageOutputIndex = null;
|
|
27
|
-
sawText = false;
|
|
28
|
-
sawToolCall = false;
|
|
29
|
-
constructor(step, ctx) {
|
|
30
|
-
this.broadcaster = getBroadcaster(ctx);
|
|
31
|
-
this.agentName = ctx.harness.config.name;
|
|
32
|
-
this.stepId = step.id;
|
|
33
|
-
this.stepKind = step.kind;
|
|
34
|
-
this.emitOption = step.emit;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Open the turn on the event surface. Emits `response.created` so every turn
|
|
38
|
-
* always brackets with a lifecycle marker — even a custom adapter that
|
|
39
|
-
* streams nothing still emits output.
|
|
40
|
-
*/
|
|
41
|
-
begin() {
|
|
42
|
-
this.start();
|
|
43
|
-
}
|
|
44
|
-
/** Forward one stream part: a structured framework event + the mapped sdk events. */
|
|
45
|
-
forward(part) {
|
|
46
|
-
if (!this.broadcaster) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const data = {
|
|
50
|
-
stepId: this.stepId,
|
|
51
|
-
kind: this.stepKind,
|
|
52
|
-
part,
|
|
53
|
-
};
|
|
54
|
-
if (shouldEmit(this.emitOption, 'sub_harness_event', data)) {
|
|
55
|
-
emitFrameworkEvent({
|
|
56
|
-
broadcaster: this.broadcaster,
|
|
57
|
-
agentName: this.agentName,
|
|
58
|
-
eventType: 'sub_harness_event',
|
|
59
|
-
data,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
this.translate(part);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Ensure the turn's output reached the event surface. For adapters that
|
|
66
|
-
* return a result without streaming, synthesize events from the result.
|
|
67
|
-
*/
|
|
68
|
-
finalize(result) {
|
|
69
|
-
if (!this.broadcaster) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (!this.sawText && result.text.length > 0) {
|
|
73
|
-
this.openMessage();
|
|
74
|
-
this.sdk('response.output_text.delta', {
|
|
75
|
-
delta: result.text,
|
|
76
|
-
}, this.messageOutputIndex ?? 0);
|
|
77
|
-
this.sawText = true;
|
|
78
|
-
}
|
|
79
|
-
if (!this.sawToolCall) {
|
|
80
|
-
for (const item of result.items) {
|
|
81
|
-
if (isFunctionCall(item)) {
|
|
82
|
-
this.emitToolCall(item.callId, item.name, item.arguments);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
this.closeMessage();
|
|
87
|
-
// Always close the turn so every turn emits a completion marker.
|
|
88
|
-
if (!this.completed) {
|
|
89
|
-
this.start();
|
|
90
|
-
this.sdk('response.completed', {});
|
|
91
|
-
this.completed = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
//#region internals
|
|
95
|
-
sdk(type, data, outputIndex) {
|
|
96
|
-
if (!this.broadcaster) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (!shouldEmit(this.emitOption, type, data)) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
this.broadcaster.emit({
|
|
103
|
-
source: 'sdk',
|
|
104
|
-
type,
|
|
105
|
-
data,
|
|
106
|
-
outputIndex,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
start() {
|
|
110
|
-
if (this.started) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
this.started = true;
|
|
114
|
-
this.sdk('response.created', {});
|
|
115
|
-
}
|
|
116
|
-
openMessage() {
|
|
117
|
-
if (this.messageOutputIndex !== null) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.start();
|
|
121
|
-
const id = `sub-harness-msg-${crypto.randomUUID()}`;
|
|
122
|
-
this.messageOutputIndex = this.nextOutputIndex++;
|
|
123
|
-
this.sdk('response.output_item.added', {
|
|
124
|
-
item: {
|
|
125
|
-
id,
|
|
126
|
-
type: 'message',
|
|
127
|
-
role: 'assistant',
|
|
128
|
-
status: 'in_progress',
|
|
129
|
-
content: [
|
|
130
|
-
{
|
|
131
|
-
type: 'output_text',
|
|
132
|
-
text: '',
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
}, this.messageOutputIndex);
|
|
137
|
-
}
|
|
138
|
-
closeMessage() {
|
|
139
|
-
if (this.messageOutputIndex === null) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const index = this.messageOutputIndex;
|
|
143
|
-
this.sdk('response.output_text.done', {}, index);
|
|
144
|
-
this.sdk('response.output_item.done', {}, index);
|
|
145
|
-
this.messageOutputIndex = null;
|
|
146
|
-
}
|
|
147
|
-
emitToolCall(toolCallId, name, input) {
|
|
148
|
-
this.start();
|
|
149
|
-
const index = this.nextOutputIndex++;
|
|
150
|
-
const args = typeof input === 'string' ? input : JSON.stringify(input ?? {});
|
|
151
|
-
this.sdk('response.output_item.added', {
|
|
152
|
-
item: {
|
|
153
|
-
id: toolCallId,
|
|
154
|
-
type: 'function_call',
|
|
155
|
-
status: 'in_progress',
|
|
156
|
-
callId: toolCallId,
|
|
157
|
-
name,
|
|
158
|
-
arguments: '',
|
|
159
|
-
},
|
|
160
|
-
}, index);
|
|
161
|
-
this.sdk('response.function_call_arguments.delta', {
|
|
162
|
-
delta: args,
|
|
163
|
-
}, index);
|
|
164
|
-
this.sdk('response.function_call_arguments.done', {
|
|
165
|
-
arguments: args,
|
|
166
|
-
}, index);
|
|
167
|
-
this.sdk('response.output_item.done', {}, index);
|
|
168
|
-
this.sawToolCall = true;
|
|
169
|
-
}
|
|
170
|
-
translate(part) {
|
|
171
|
-
if (part.type === 'stream-start') {
|
|
172
|
-
this.start();
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (part.type === 'text-delta') {
|
|
176
|
-
this.openMessage();
|
|
177
|
-
this.sdk('response.output_text.delta', {
|
|
178
|
-
delta: part.delta,
|
|
179
|
-
}, this.messageOutputIndex ?? 0);
|
|
180
|
-
this.sawText = true;
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (part.type === 'reasoning-delta') {
|
|
184
|
-
this.start();
|
|
185
|
-
this.sdk('response.reasoning.delta', {
|
|
186
|
-
delta: part.delta,
|
|
187
|
-
});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (part.type === 'tool-call') {
|
|
191
|
-
this.closeMessage();
|
|
192
|
-
this.emitToolCall(part.toolCallId, part.toolName, part.input);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (part.type === 'finish') {
|
|
196
|
-
this.closeMessage();
|
|
197
|
-
this.sdk('response.completed', {
|
|
198
|
-
finishReason: part.finishReason,
|
|
199
|
-
});
|
|
200
|
-
this.completed = true;
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
// file-change | tool-result | error | raw — surfaced on the full stream only.
|
|
204
|
-
this.sdk(`sub_harness.${part.type}`, {
|
|
205
|
-
part,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|