@inflexa-ai/harness 0.40.0 → 0.41.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/app/chat-turn.d.ts +42 -13
- package/dist/app/chat-turn.js +47 -16
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/app/chat-turn.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The chat turn of a host: a turn is `runChatTurn`,
|
|
3
|
-
*
|
|
2
|
+
* The chat turn of a host: a turn is `runChatTurn`, which is `openChatTurn` and then the `run` of the
|
|
3
|
+
* open turn. `prepareChatTurn` is the first step of the open. The host gives only its transport
|
|
4
|
+
* values, and the harness stores the opening, each round, and the outcome.
|
|
4
5
|
*/
|
|
5
6
|
import type { Pool } from "pg";
|
|
6
|
-
import type {
|
|
7
|
+
import type { RequestSession } from "../auth/types.js";
|
|
7
8
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
8
9
|
import type { TokenUsageRollup } from "../contracts/usage.js";
|
|
9
10
|
import type { DbError } from "../lib/db-result.js";
|
|
10
11
|
import { type AgentFinish } from "../loop/run-agent.js";
|
|
11
|
-
import type { EmitFn } from "../loop/types.js";
|
|
12
|
+
import type { AgentDefinition, EmitFn } from "../loop/types.js";
|
|
12
13
|
import { type ThreadType } from "../memory/thread-store.js";
|
|
13
14
|
import type { AgentChat, PromptCachePolicy } from "../providers/types.js";
|
|
14
15
|
import type { ThreadAgentResolver } from "../runtime/assemble.js";
|
|
@@ -54,12 +55,20 @@ export interface RunChatTurnDeps extends PrepareChatTurnDeps {
|
|
|
54
55
|
/** The agent of a turn comes from the thread type, which only the preparation knows. */
|
|
55
56
|
readonly agents: ThreadAgentResolver;
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The session of a turn without its provenance. The harness sets the provenance from the agent that
|
|
60
|
+
* it resolves, because only the preparation knows the thread type.
|
|
61
|
+
*/
|
|
62
|
+
export type ChatTurnSession = Omit<RequestSession, "provenance">;
|
|
63
|
+
/** What opens a turn: the thread and the user input. */
|
|
64
|
+
export interface OpenChatTurnParams {
|
|
59
65
|
readonly analysisId: string;
|
|
60
66
|
readonly threadId: string;
|
|
61
67
|
readonly userInput: string;
|
|
62
|
-
|
|
68
|
+
}
|
|
69
|
+
/** The transport values of a turn that is open. */
|
|
70
|
+
export interface RunOpenChatTurnParams {
|
|
71
|
+
readonly session: ChatTurnSession;
|
|
63
72
|
/** Makes the provider of the turn over the emit sink of the display recorder. */
|
|
64
73
|
readonly chat: (emit: EmitFn) => AgentChat;
|
|
65
74
|
readonly emit: EmitFn;
|
|
@@ -69,13 +78,15 @@ export interface RunChatTurnParams {
|
|
|
69
78
|
readonly ask?: (request: AskRequest, emit: EmitFn) => Promise<AskApproval>;
|
|
70
79
|
/** Who sent the user message. */
|
|
71
80
|
readonly author?: string;
|
|
72
|
-
/** When the host opened the turn. Absent counts from the call. */
|
|
81
|
+
/** When the host opened the turn. Absent counts from the call to {@link openChatTurn}. */
|
|
73
82
|
readonly startedAtMs?: number;
|
|
74
83
|
/** The cache policy of the root loop. Absent gives {@link CONVERSATION_PROMPT_CACHE}. */
|
|
75
84
|
readonly promptCache?: PromptCachePolicy;
|
|
76
85
|
/** The budget of the view of the root loop. Absent gives {@link DEFAULT_CONVERSATION_BUDGET}. */
|
|
77
86
|
readonly conversationBudget?: number;
|
|
78
87
|
}
|
|
88
|
+
/** The values of one whole turn. */
|
|
89
|
+
export type RunChatTurnParams = OpenChatTurnParams & RunOpenChatTurnParams;
|
|
79
90
|
export type ChatTurnOutcome = {
|
|
80
91
|
readonly status: "done";
|
|
81
92
|
readonly finish: AgentFinish;
|
|
@@ -87,7 +98,8 @@ export type ChatTurnOutcome = {
|
|
|
87
98
|
readonly reason: string;
|
|
88
99
|
readonly cause: unknown;
|
|
89
100
|
};
|
|
90
|
-
|
|
101
|
+
/** A turn that ended before its run. It wrote no row and no turn record. */
|
|
102
|
+
export type ChatTurnRefusal = {
|
|
91
103
|
readonly kind: "prepare_failed";
|
|
92
104
|
readonly cause: unknown;
|
|
93
105
|
} | {
|
|
@@ -95,7 +107,8 @@ export type ChatTurnResult = {
|
|
|
95
107
|
} | {
|
|
96
108
|
readonly kind: "agent_unresolved";
|
|
97
109
|
readonly threadType: ThreadType;
|
|
98
|
-
}
|
|
110
|
+
};
|
|
111
|
+
export interface ChatTurnRan {
|
|
99
112
|
readonly kind: "ran";
|
|
100
113
|
readonly outcome: ChatTurnOutcome;
|
|
101
114
|
/** True when the opening of the turn landed. */
|
|
@@ -106,9 +119,25 @@ export type ChatTurnResult = {
|
|
|
106
119
|
readonly turnUsage?: TokenUsageRollup;
|
|
107
120
|
/** The final text of a run that returned. */
|
|
108
121
|
readonly fallbackText?: string;
|
|
109
|
-
}
|
|
122
|
+
}
|
|
123
|
+
export type ChatTurnResult = ChatTurnRefusal | ChatTurnRan;
|
|
124
|
+
/**
|
|
125
|
+
* A turn that is prepared, with its agent resolved. Call `run` one time: each call stores a new
|
|
126
|
+
* opening of the same user message.
|
|
127
|
+
*/
|
|
128
|
+
export interface OpenChatTurn {
|
|
129
|
+
readonly kind: "ready";
|
|
130
|
+
/** The agent that runs the turn. The harness stamps its provenance on the session of the run. */
|
|
131
|
+
readonly agent: AgentDefinition;
|
|
132
|
+
readonly run: (params: RunOpenChatTurnParams) => Promise<ChatTurnRan>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Open one chat turn: prepare it and resolve its agent. A refusal comes back before any row of the
|
|
136
|
+
* turn exists, thus a host can answer it before it starts a stream.
|
|
137
|
+
*/
|
|
138
|
+
export declare function openChatTurn(deps: RunChatTurnDeps, params: OpenChatTurnParams): Promise<OpenChatTurn | ChatTurnRefusal>;
|
|
110
139
|
/**
|
|
111
|
-
* Run one chat turn:
|
|
112
|
-
*
|
|
140
|
+
* Run one chat turn: open it, store its opening, run the root loop with a round sink that stores
|
|
141
|
+
* each round, and close the turn with its outcome.
|
|
113
142
|
*/
|
|
114
143
|
export declare function runChatTurn(deps: RunChatTurnDeps, params: RunChatTurnParams): Promise<ChatTurnResult>;
|
package/dist/app/chat-turn.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The chat turn of a host: a turn is `runChatTurn`,
|
|
3
|
-
*
|
|
2
|
+
* The chat turn of a host: a turn is `runChatTurn`, which is `openChatTurn` and then the `run` of the
|
|
3
|
+
* open turn. `prepareChatTurn` is the first step of the open. The host gives only its transport
|
|
4
|
+
* values, and the harness stores the opening, each round, and the outcome.
|
|
4
5
|
*/
|
|
5
6
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
6
7
|
import { finalText, runAgent } from "../loop/run-agent.js";
|
|
@@ -100,15 +101,14 @@ async function readTurnContext(pool, analysisId) {
|
|
|
100
101
|
/** The root loop of a chat turn compacts its view past this estimate, in tokens. */
|
|
101
102
|
export const DEFAULT_CONVERSATION_BUDGET = 150_000;
|
|
102
103
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
104
|
+
* Open one chat turn: prepare it and resolve its agent. A refusal comes back before any row of the
|
|
105
|
+
* turn exists, thus a host can answer it before it starts a stream.
|
|
105
106
|
*/
|
|
106
|
-
export async function
|
|
107
|
-
const
|
|
108
|
-
const startedAtMs = params.startedAtMs ?? Date.now();
|
|
107
|
+
export async function openChatTurn(deps, params) {
|
|
108
|
+
const openedAtMs = Date.now();
|
|
109
109
|
let prepared;
|
|
110
110
|
try {
|
|
111
|
-
prepared = await prepareChatTurn(deps,
|
|
111
|
+
prepared = await prepareChatTurn(deps, params);
|
|
112
112
|
}
|
|
113
113
|
catch (cause) {
|
|
114
114
|
return { kind: "prepare_failed", cause };
|
|
@@ -118,8 +118,33 @@ export async function runChatTurn(deps, params) {
|
|
|
118
118
|
const agent = deps.agents.forThread(prepared.threadType);
|
|
119
119
|
if (agent.isErr())
|
|
120
120
|
return { kind: "agent_unresolved", threadType: prepared.threadType };
|
|
121
|
+
const ready = prepared;
|
|
122
|
+
return {
|
|
123
|
+
kind: "ready",
|
|
124
|
+
agent: agent.value,
|
|
125
|
+
run: (run) => runOpenTurn(deps, params, ready, agent.value, { ...run, startedAtMs: run.startedAtMs ?? openedAtMs }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run one chat turn: open it, store its opening, run the root loop with a round sink that stores
|
|
130
|
+
* each round, and close the turn with its outcome.
|
|
131
|
+
*/
|
|
132
|
+
export async function runChatTurn(deps, params) {
|
|
133
|
+
const { analysisId, threadId, userInput, ...run } = params;
|
|
134
|
+
const opened = await openChatTurn(deps, { analysisId, threadId, userInput });
|
|
135
|
+
if (opened.kind !== "ready")
|
|
136
|
+
return opened;
|
|
137
|
+
return opened.run(run);
|
|
138
|
+
}
|
|
139
|
+
async function runOpenTurn(deps, turn, prepared, agent, params) {
|
|
140
|
+
const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
|
|
141
|
+
const session = { ...params.session, provenance: { agentId: agent.id, callPath: [agent.id] } };
|
|
121
142
|
const history = createThreadHistory(deps.pool, deps.logger);
|
|
122
|
-
const recorder = createConversationDisplayRecorder({
|
|
143
|
+
const recorder = createConversationDisplayRecorder({
|
|
144
|
+
userText: userTextOf(prepared.userMessage),
|
|
145
|
+
topLevelCallPath: session.provenance.callPath,
|
|
146
|
+
sink: params.emit,
|
|
147
|
+
});
|
|
123
148
|
// The groups that did not land yet, in the order of the turn. A failed write keeps them for the next write.
|
|
124
149
|
let pending = [
|
|
125
150
|
{
|
|
@@ -136,13 +161,13 @@ export async function runChatTurn(deps, params) {
|
|
|
136
161
|
return;
|
|
137
162
|
const closing = close === undefined ? {} : { close };
|
|
138
163
|
const write = startSeq === undefined ? { opening: pending[0], rounds: pending.slice(1), ...closing } : { startSeq, rounds: pending, ...closing };
|
|
139
|
-
(await history.writeTurn(
|
|
164
|
+
(await history.writeTurn(turn.threadId, write)).match((written) => {
|
|
140
165
|
pending = [];
|
|
141
166
|
startSeq = written.startSeq;
|
|
142
167
|
storeError = undefined;
|
|
143
168
|
}, (error) => {
|
|
144
169
|
storeError = error;
|
|
145
|
-
logger.warn("chat turn write failed", { threadId:
|
|
170
|
+
logger.warn("chat turn write failed", { threadId: turn.threadId, op: error.op, ...logger.errorFields(error.cause) });
|
|
146
171
|
});
|
|
147
172
|
};
|
|
148
173
|
await flush();
|
|
@@ -150,7 +175,7 @@ export async function runChatTurn(deps, params) {
|
|
|
150
175
|
let outcome;
|
|
151
176
|
let fallbackText;
|
|
152
177
|
try {
|
|
153
|
-
const run = await runAgent(agent
|
|
178
|
+
const run = await runAgent(agent, prepared.messages, session, {
|
|
154
179
|
provider: params.chat(recorder.emit),
|
|
155
180
|
signal: params.signal,
|
|
156
181
|
emit: recorder.emit,
|
|
@@ -158,7 +183,7 @@ export async function runChatTurn(deps, params) {
|
|
|
158
183
|
usageRecorder: params.usageRecorder,
|
|
159
184
|
toolOutputStore: createToolOutputStore(deps.pool),
|
|
160
185
|
promptCache: params.promptCache ?? CONVERSATION_PROMPT_CACHE,
|
|
161
|
-
compaction: compactionPolicy(deps, params, prepared.threadType, agent
|
|
186
|
+
compaction: compactionPolicy(deps, turn.analysisId, params, prepared.threadType, agent, recorder.emit),
|
|
162
187
|
...(deps.logger === undefined ? {} : { logger: deps.logger }),
|
|
163
188
|
...(ask === undefined ? {} : { ask: (request) => ask(request, recorder.emit) }),
|
|
164
189
|
onRound: async (round) => {
|
|
@@ -175,7 +200,7 @@ export async function runChatTurn(deps, params) {
|
|
|
175
200
|
const aborted = params.signal.aborted && cause instanceof Error && cause.name === "AbortError";
|
|
176
201
|
outcome = aborted ? { status: "aborted" } : { status: "failed", reason: failureReasonOf(cause), cause };
|
|
177
202
|
}
|
|
178
|
-
const durationMs = Date.now() - startedAtMs;
|
|
203
|
+
const durationMs = Date.now() - params.startedAtMs;
|
|
179
204
|
const turnUsage = outcome.status === "failed" ? undefined : outcome.finish?.turnUsage;
|
|
180
205
|
await flush({
|
|
181
206
|
status: outcome.status,
|
|
@@ -193,8 +218,14 @@ export async function runChatTurn(deps, params) {
|
|
|
193
218
|
...(fallbackText === undefined ? {} : { fallbackText }),
|
|
194
219
|
};
|
|
195
220
|
}
|
|
221
|
+
/** The sanitized text of the user message, thus the display never holds a secret that the model does not see. */
|
|
222
|
+
function userTextOf(message) {
|
|
223
|
+
if (typeof message.content === "string")
|
|
224
|
+
return message.content;
|
|
225
|
+
return message.content.map((part) => (part.type === "text" ? part.text : "")).join("");
|
|
226
|
+
}
|
|
196
227
|
const WORKING_MEMORY_TOOL_ID = "update_working_memory";
|
|
197
|
-
function compactionPolicy(deps, params, threadType, agent, emit) {
|
|
228
|
+
function compactionPolicy(deps, analysisId, params, threadType, agent, emit) {
|
|
198
229
|
// A report thread reads a frozen copy of the working memory, thus its agent declares no memory tool.
|
|
199
230
|
const remembers = agent.tools.some((tool) => tool.id === WORKING_MEMORY_TOOL_ID);
|
|
200
231
|
const workingMemory = createWorkingMemory(deps.pool);
|
|
@@ -205,7 +236,7 @@ function compactionPolicy(deps, params, threadType, agent, emit) {
|
|
|
205
236
|
request: remembers ? MEMORY_COMPACTION_REQUEST : SUMMARY_COMPACTION_REQUEST,
|
|
206
237
|
mask: remembers ? { allow: [WORKING_MEMORY_TOOL_ID] } : "none",
|
|
207
238
|
keepFirstTurn: threadType === "report",
|
|
208
|
-
recordsAfter: async (view) => contextRecordsFor({ threadType, analysisId
|
|
239
|
+
recordsAfter: async (view) => contextRecordsFor({ threadType, analysisId, workingMemory, ...(await readTurnContext(deps.pool, analysisId)) }, view),
|
|
209
240
|
};
|
|
210
241
|
}
|
|
211
242
|
/** The reason of a failed turn. It never holds the error message, because each later turn sends the note to the vendor. */
|
package/dist/index.d.ts
CHANGED
|
@@ -82,8 +82,8 @@ export type { SuspendOn } from "./providers/errors.js";
|
|
|
82
82
|
export { suspensionOfFailure } from "./workflows/suspension.js";
|
|
83
83
|
export type { Suspension } from "./workflows/suspension.js";
|
|
84
84
|
export { createStreamingChat } from "./providers/streaming-chat.js";
|
|
85
|
-
export { DEFAULT_CONVERSATION_BUDGET, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
|
|
86
|
-
export type { ChatTurnOutcome, ChatTurnResult, PrepareChatTurnDeps, PrepareChatTurnParams, PrepareChatTurnResult, RunChatTurnDeps, RunChatTurnParams, } from "./app/chat-turn.js";
|
|
85
|
+
export { DEFAULT_CONVERSATION_BUDGET, openChatTurn, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
|
|
86
|
+
export type { ChatTurnOutcome, ChatTurnRan, ChatTurnRefusal, ChatTurnResult, ChatTurnSession, OpenChatTurn, OpenChatTurnParams, PrepareChatTurnDeps, PrepareChatTurnParams, PrepareChatTurnResult, RunChatTurnDeps, RunChatTurnParams, RunOpenChatTurnParams, } from "./app/chat-turn.js";
|
|
87
87
|
export { createThreadStore } from "./memory/thread-store.js";
|
|
88
88
|
export type { ThreadStore, Thread, ThreadType, ThreadInputError, CreateThreadInput, ListThreadsInput, ThreadPage } from "./memory/thread-store.js";
|
|
89
89
|
export { createThreadHistory, conversationRecordTurn } from "./memory/thread-history.js";
|
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ export { createStreamingChat } from "./providers/streaming-chat.js";
|
|
|
130
130
|
// and gives only its transport values: the harness stores the opening, each round,
|
|
131
131
|
// and the outcome. `appendTurn` stays for a record of a host. `createThreadStore`
|
|
132
132
|
// owns thread metadata (create/list/title).
|
|
133
|
-
export { DEFAULT_CONVERSATION_BUDGET, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
|
|
133
|
+
export { DEFAULT_CONVERSATION_BUDGET, openChatTurn, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
|
|
134
134
|
export { createThreadStore } from "./memory/thread-store.js";
|
|
135
135
|
export { createThreadHistory, conversationRecordTurn } from "./memory/thread-history.js";
|
|
136
136
|
// The synthetic-message primitives. A host appends a record of out-of-band work (an analysis run's
|
package/package.json
CHANGED