@inflexa-ai/harness 0.18.0 → 0.19.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/agents/report-session-agent.d.ts +56 -0
- package/dist/agents/report-session-agent.js +72 -0
- package/dist/app/report-session-runtime.d.ts +72 -0
- package/dist/app/report-session-runtime.js +157 -0
- package/dist/memory/thread-store.d.ts +4 -4
- package/dist/memory/thread-store.js +18 -9
- package/dist/prompts/report-session.d.ts +1 -0
- package/dist/prompts/report-session.js +89 -0
- package/dist/providers/ai-sdk.d.ts +77 -0
- package/dist/providers/ai-sdk.js +149 -19
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +103 -18
- package/dist/providers/types.d.ts +16 -0
- package/dist/providers/types.js +10 -1
- package/dist/report-model/{mint-snapshot.d.ts → pin-snapshot.d.ts} +7 -7
- package/dist/report-model/{mint-snapshot.js → pin-snapshot.js} +7 -7
- package/dist/report-model/reference-resolver.d.ts +1 -1
- package/dist/report-model/validate.d.ts +34 -2
- package/dist/report-model/validate.js +32 -17
- package/dist/report-render/value-bridge.d.ts +62 -0
- package/dist/report-render/value-bridge.js +82 -0
- package/dist/runtime/assemble.d.ts +15 -0
- package/dist/runtime/assemble.js +28 -6
- package/dist/state/init.js +23 -0
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-session-state.d.ts +112 -0
- package/dist/state/report-session-state.js +150 -0
- package/dist/state/report-versions.d.ts +3 -6
- package/dist/state/report-versions.js +11 -38
- package/dist/state/snapshot-parse.d.ts +34 -0
- package/dist/state/snapshot-parse.js +82 -0
- package/dist/tools/ad-hoc-router.d.ts +7 -2
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/report-authoring/authoring-tools.d.ts +131 -24
- package/dist/tools/report-authoring/authoring-tools.js +152 -44
- package/dist/tools/report-session/index.d.ts +8 -0
- package/dist/tools/report-session/index.js +8 -0
- package/dist/tools/report-session/preview-report.d.ts +90 -0
- package/dist/tools/report-session/preview-report.js +245 -0
- package/dist/tools/research/generate-plan.js +4 -2
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Report Builder agent -- the agent that talks to the user on a report thread.
|
|
3
|
+
*
|
|
4
|
+
* `createReportSessionAgent` is the composition root of the report path. It gets
|
|
5
|
+
* the shared deps one time, and it hands each tool exactly what it needs. The
|
|
6
|
+
* authoring tools and the preview tool bind to the session-state gateway, thus one
|
|
7
|
+
* assembled definition serves every thread, and the per-session state stays behind
|
|
8
|
+
* the tool boundary.
|
|
9
|
+
*
|
|
10
|
+
* The roster is read-only toward the analysis. It holds the workspace read tools,
|
|
11
|
+
* the workspace search, the run inspection, and the data-profile inspection. It
|
|
12
|
+
* holds no planner, no run launcher, no working-memory write, and no sandbox
|
|
13
|
+
* mutate surface. Thus no tool starts a run, and no tool changes an analysis. The
|
|
14
|
+
* roster omits those tools, and no runtime guard blocks them.
|
|
15
|
+
*
|
|
16
|
+
* The definition carries no per-session value. The system prompt composes through
|
|
17
|
+
* `composeSystemPrompt` with the identity part and the conversational part, the
|
|
18
|
+
* same composition as the conversation agent. Thus the prompt prefix stays constant
|
|
19
|
+
* across threads, and the provider cache reuses it.
|
|
20
|
+
*/
|
|
21
|
+
import type { Pool } from "pg";
|
|
22
|
+
import type { AgentDefinition } from "../loop/types.js";
|
|
23
|
+
import type { EmbeddingProvider } from "../providers/types.js";
|
|
24
|
+
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
25
|
+
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
26
|
+
import type { Logger } from "../lib/logger.js";
|
|
27
|
+
import type { ReferenceResolver } from "../report-model/reference-resolver.js";
|
|
28
|
+
import type { ReportSessionStateGateway } from "../tools/report-authoring/authoring-tools.js";
|
|
29
|
+
/** Canonical agent id -- distinct from the id of the old templating agent. */
|
|
30
|
+
export declare const REPORT_SESSION_AGENT_ID: "report-session";
|
|
31
|
+
/**
|
|
32
|
+
* The shared dependencies of the report agent. The gateway binds the per-session
|
|
33
|
+
* state to the thread, thus every tool that touches the state gets the same
|
|
34
|
+
* gateway. The resolver is optional, because a resolver realization can be absent,
|
|
35
|
+
* and the preview tool degrades as data when it is.
|
|
36
|
+
*/
|
|
37
|
+
export interface ReportSessionAgentDeps {
|
|
38
|
+
/** Model id -- the provenance and metric label. The provider owns the wire model. */
|
|
39
|
+
readonly model: string;
|
|
40
|
+
/** Postgres pool -- workspace index, run inspection, and data-profile inspection. */
|
|
41
|
+
readonly pool: Pool;
|
|
42
|
+
/** Embedding seam -- workspace semantic search. */
|
|
43
|
+
readonly embedding: EmbeddingProvider;
|
|
44
|
+
/** Workspace filesystem read seam -- the four read tools. */
|
|
45
|
+
readonly workspaceFs: WorkspaceFilesystem;
|
|
46
|
+
/** Session-state gateway -- the authoring tools and the preview tool bind to it. */
|
|
47
|
+
readonly gateway: ReportSessionStateGateway;
|
|
48
|
+
/** Workspace-root resolution seam -- the preview tool resolves the page root per call. */
|
|
49
|
+
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
50
|
+
/** Reference-resolution seam -- absent until a realization lands; the preview tool degrades as data. */
|
|
51
|
+
readonly resolver?: ReferenceResolver;
|
|
52
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
53
|
+
readonly logger?: Logger;
|
|
54
|
+
}
|
|
55
|
+
/** Build the report `AgentDefinition` with every tool bound to its deps. */
|
|
56
|
+
export declare function createReportSessionAgent(deps: ReportSessionAgentDeps): AgentDefinition;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Report Builder agent -- the agent that talks to the user on a report thread.
|
|
3
|
+
*
|
|
4
|
+
* `createReportSessionAgent` is the composition root of the report path. It gets
|
|
5
|
+
* the shared deps one time, and it hands each tool exactly what it needs. The
|
|
6
|
+
* authoring tools and the preview tool bind to the session-state gateway, thus one
|
|
7
|
+
* assembled definition serves every thread, and the per-session state stays behind
|
|
8
|
+
* the tool boundary.
|
|
9
|
+
*
|
|
10
|
+
* The roster is read-only toward the analysis. It holds the workspace read tools,
|
|
11
|
+
* the workspace search, the run inspection, and the data-profile inspection. It
|
|
12
|
+
* holds no planner, no run launcher, no working-memory write, and no sandbox
|
|
13
|
+
* mutate surface. Thus no tool starts a run, and no tool changes an analysis. The
|
|
14
|
+
* roster omits those tools, and no runtime guard blocks them.
|
|
15
|
+
*
|
|
16
|
+
* The definition carries no per-session value. The system prompt composes through
|
|
17
|
+
* `composeSystemPrompt` with the identity part and the conversational part, the
|
|
18
|
+
* same composition as the conversation agent. Thus the prompt prefix stays constant
|
|
19
|
+
* across threads, and the provider cache reuses it.
|
|
20
|
+
*/
|
|
21
|
+
import { createReportAuthoringTools } from "../tools/report-authoring/authoring-tools.js";
|
|
22
|
+
import { createPreviewReportTool } from "../tools/report-session/preview-report.js";
|
|
23
|
+
import { createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createWorkspaceSearchTool } from "../tools/workspace/index.js";
|
|
24
|
+
import { createInspectDataProfileTool, createInspectRunTool } from "../tools/research/index.js";
|
|
25
|
+
import { reportSessionPrompt } from "../prompts/report-session.js";
|
|
26
|
+
import { composeSystemPrompt } from "./system-prompt.js";
|
|
27
|
+
/** Canonical agent id -- distinct from the id of the old templating agent. */
|
|
28
|
+
export const REPORT_SESSION_AGENT_ID = "report-session";
|
|
29
|
+
/**
|
|
30
|
+
* Runaway guard. A report turn drives many small tool calls, thus it needs the
|
|
31
|
+
* same headroom as the conversation agent.
|
|
32
|
+
*/
|
|
33
|
+
const REPORT_SESSION_MAX_ITERATIONS = 50;
|
|
34
|
+
/** Build the report `AgentDefinition` with every tool bound to its deps. */
|
|
35
|
+
export function createReportSessionAgent(deps) {
|
|
36
|
+
const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, resolver, logger } = deps;
|
|
37
|
+
const authoring = createReportAuthoringTools(gateway);
|
|
38
|
+
const tools = [
|
|
39
|
+
// The read surface toward the analysis. It roams the tree read-only.
|
|
40
|
+
createReadFileTool(workspaceFs),
|
|
41
|
+
createListFilesTool(workspaceFs),
|
|
42
|
+
createFileStatTool(workspaceFs),
|
|
43
|
+
createGrepTool(workspaceFs),
|
|
44
|
+
createWorkspaceSearchTool(pool, embedding),
|
|
45
|
+
createInspectRunTool(pool),
|
|
46
|
+
createInspectDataProfileTool(pool),
|
|
47
|
+
// The composition surface. Each authoring tool binds to the gateway, thus
|
|
48
|
+
// two threads never share one draft.
|
|
49
|
+
authoring.add_block,
|
|
50
|
+
authoring.change_block,
|
|
51
|
+
authoring.remove_block,
|
|
52
|
+
authoring.move_block,
|
|
53
|
+
authoring.set_title,
|
|
54
|
+
authoring.read_outline,
|
|
55
|
+
authoring.read_block,
|
|
56
|
+
authoring.finish_draft,
|
|
57
|
+
// The render-and-preview tool. It resolves the page root per call.
|
|
58
|
+
createPreviewReportTool({
|
|
59
|
+
gateway,
|
|
60
|
+
resolveWorkspaceRoot,
|
|
61
|
+
...(resolver ? { resolver } : {}),
|
|
62
|
+
...(logger ? { logger } : {}),
|
|
63
|
+
}),
|
|
64
|
+
];
|
|
65
|
+
return {
|
|
66
|
+
id: REPORT_SESSION_AGENT_ID,
|
|
67
|
+
systemPrompt: composeSystemPrompt(reportSessionPrompt, { identity: true, conversational: true }),
|
|
68
|
+
model,
|
|
69
|
+
tools,
|
|
70
|
+
maxIterations: REPORT_SESSION_MAX_ITERATIONS,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report session-state runtime -- the durable realization of the session-state
|
|
3
|
+
* gateway, and the idempotent operation that anchors one report session.
|
|
4
|
+
*
|
|
5
|
+
* The runtime binds three parts: the session-state store, the thread store, and
|
|
6
|
+
* the pin. The tool layer speaks the gateway, and the gateway speaks a plain
|
|
7
|
+
* discriminated value. Thus the neverthrow of the store stays inside this module,
|
|
8
|
+
* and it never crosses the tool boundary.
|
|
9
|
+
*
|
|
10
|
+
* `ensureSessionState` makes sure that the row of a thread exists. The first run
|
|
11
|
+
* resolves the analysis of the thread, pins the snapshot, and writes the row. A
|
|
12
|
+
* later run reads the row and pins nothing. The store insert-if-absent keeps two
|
|
13
|
+
* concurrent first runs to one row. A pin failure writes no row and returns as
|
|
14
|
+
* typed data, thus a later run pins again.
|
|
15
|
+
*
|
|
16
|
+
* The gateway `load` runs the same operation, thus a tool call never arrives
|
|
17
|
+
* before the state exists. A fresh row holds the snapshot and no document yet.
|
|
18
|
+
* Thus `load` gives the empty draft with the stored snapshot, because the pin
|
|
19
|
+
* writes the snapshot first and the document lands later.
|
|
20
|
+
*
|
|
21
|
+
* A store fault, an absent thread, and a pin failure each ride the failed arm
|
|
22
|
+
* with a short detail. The diagnostic goes to the injected `Logger`, never to the
|
|
23
|
+
* detail alone.
|
|
24
|
+
*
|
|
25
|
+
* The gateway load separates a permanent condition from a transient one. An absent
|
|
26
|
+
* thread and a wrong thread type are permanent, and a store or pin fault is
|
|
27
|
+
* transient. The persist is a compare-and-swap against the prior document that the
|
|
28
|
+
* load read, thus two concurrent turns cannot both land.
|
|
29
|
+
*/
|
|
30
|
+
import type { Pool } from "pg";
|
|
31
|
+
import type { Logger } from "../lib/logger.js";
|
|
32
|
+
import { type ReportSessionState as StoredSessionState } from "../state/report-session-state.js";
|
|
33
|
+
import type { ReportSessionStateGateway } from "../tools/report-authoring/authoring-tools.js";
|
|
34
|
+
/**
|
|
35
|
+
* How a failed anchor ends. `absent-thread` and `wrong-thread-type` are permanent
|
|
36
|
+
* conditions, and `unavailable` is a transient store or pin fault. The gateway load
|
|
37
|
+
* carries the distinction, thus the tool tells a permanent refusal from a transient one.
|
|
38
|
+
*/
|
|
39
|
+
export type EnsureFailureKind = "absent-thread" | "wrong-thread-type" | "unavailable";
|
|
40
|
+
/**
|
|
41
|
+
* The outcome of the anchor operation. `ready` carries the durable row, whose
|
|
42
|
+
* document is `null` until the first document lands. `failed` names the cause as a
|
|
43
|
+
* short detail and a kind, and the runtime logs the diagnostic beside it.
|
|
44
|
+
*/
|
|
45
|
+
export type EnsureSessionStateResult = {
|
|
46
|
+
outcome: "ready";
|
|
47
|
+
state: StoredSessionState;
|
|
48
|
+
} | {
|
|
49
|
+
outcome: "failed";
|
|
50
|
+
kind: EnsureFailureKind;
|
|
51
|
+
detail: string;
|
|
52
|
+
};
|
|
53
|
+
export interface ReportSessionRuntimeDeps {
|
|
54
|
+
readonly pool: Pool;
|
|
55
|
+
readonly logger?: Logger;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The report session-state runtime. The gateway serves the authoring tools, and
|
|
59
|
+
* `ensureSessionState` anchors the session. The serving path of a report turn runs
|
|
60
|
+
* `ensureSessionState` at the turn start, and the gateway `load` runs it too, thus
|
|
61
|
+
* a tool call cannot arrive before the state exists.
|
|
62
|
+
*/
|
|
63
|
+
export interface ReportSessionRuntime {
|
|
64
|
+
readonly gateway: ReportSessionStateGateway;
|
|
65
|
+
ensureSessionState(threadId: string): Promise<EnsureSessionStateResult>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the report session-state runtime bound to a Postgres pool. The factory
|
|
69
|
+
* closure captures the pool and constructs the session-state store and the thread
|
|
70
|
+
* store from it, the same way the chat-turn preparation does.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createReportSessionRuntime(deps: ReportSessionRuntimeDeps): ReportSessionRuntime;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The report session-state runtime -- the durable realization of the session-state
|
|
3
|
+
* gateway, and the idempotent operation that anchors one report session.
|
|
4
|
+
*
|
|
5
|
+
* The runtime binds three parts: the session-state store, the thread store, and
|
|
6
|
+
* the pin. The tool layer speaks the gateway, and the gateway speaks a plain
|
|
7
|
+
* discriminated value. Thus the neverthrow of the store stays inside this module,
|
|
8
|
+
* and it never crosses the tool boundary.
|
|
9
|
+
*
|
|
10
|
+
* `ensureSessionState` makes sure that the row of a thread exists. The first run
|
|
11
|
+
* resolves the analysis of the thread, pins the snapshot, and writes the row. A
|
|
12
|
+
* later run reads the row and pins nothing. The store insert-if-absent keeps two
|
|
13
|
+
* concurrent first runs to one row. A pin failure writes no row and returns as
|
|
14
|
+
* typed data, thus a later run pins again.
|
|
15
|
+
*
|
|
16
|
+
* The gateway `load` runs the same operation, thus a tool call never arrives
|
|
17
|
+
* before the state exists. A fresh row holds the snapshot and no document yet.
|
|
18
|
+
* Thus `load` gives the empty draft with the stored snapshot, because the pin
|
|
19
|
+
* writes the snapshot first and the document lands later.
|
|
20
|
+
*
|
|
21
|
+
* A store fault, an absent thread, and a pin failure each ride the failed arm
|
|
22
|
+
* with a short detail. The diagnostic goes to the injected `Logger`, never to the
|
|
23
|
+
* detail alone.
|
|
24
|
+
*
|
|
25
|
+
* The gateway load separates a permanent condition from a transient one. An absent
|
|
26
|
+
* thread and a wrong thread type are permanent, and a store or pin fault is
|
|
27
|
+
* transient. The persist is a compare-and-swap against the prior document that the
|
|
28
|
+
* load read, thus two concurrent turns cannot both land.
|
|
29
|
+
*/
|
|
30
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
31
|
+
import { describeDbError } from "../lib/db-result.js";
|
|
32
|
+
import { createThreadStore } from "../memory/thread-store.js";
|
|
33
|
+
import { pinReportSnapshot } from "../report-model/pin-snapshot.js";
|
|
34
|
+
import { createReportSessionStateStore } from "../state/report-session-state.js";
|
|
35
|
+
/** The empty draft of a fresh session. The pin writes the snapshot first, thus the document is empty here. */
|
|
36
|
+
const EMPTY_DRAFT = { title: "", sections: [] };
|
|
37
|
+
/**
|
|
38
|
+
* Build the report session-state runtime bound to a Postgres pool. The factory
|
|
39
|
+
* closure captures the pool and constructs the session-state store and the thread
|
|
40
|
+
* store from it, the same way the chat-turn preparation does.
|
|
41
|
+
*/
|
|
42
|
+
export function createReportSessionRuntime(deps) {
|
|
43
|
+
const { pool } = deps;
|
|
44
|
+
const log = (deps.logger ?? createNoopLogger()).named("report-session-runtime");
|
|
45
|
+
const store = createReportSessionStateStore({ pool, logger: deps.logger });
|
|
46
|
+
const threads = createThreadStore(pool);
|
|
47
|
+
/**
|
|
48
|
+
* A store read or write fault, as a failed outcome. `corrupt_session_state`
|
|
49
|
+
* names the part that cannot parse. A `DbError` carries its cause to the log.
|
|
50
|
+
* The identifiers ride as structured fields, never in the message.
|
|
51
|
+
*/
|
|
52
|
+
function storeFault(threadId, error) {
|
|
53
|
+
if (error.type === "corrupt_session_state") {
|
|
54
|
+
log.error("the stored session state cannot parse", { threadId, part: error.part });
|
|
55
|
+
return { outcome: "failed", kind: "unavailable", detail: `the stored ${error.part} of the session state cannot parse` };
|
|
56
|
+
}
|
|
57
|
+
log.error("the session-state store failed", { threadId, ...log.errorFields(error.cause) });
|
|
58
|
+
return { outcome: "failed", kind: "unavailable", detail: describeDbError(error) };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pin the snapshot and write the row. A pin failure writes no row, thus a
|
|
62
|
+
* later run pins again. The write is insert-if-absent, thus two concurrent
|
|
63
|
+
* first runs make one row and both read the winner back.
|
|
64
|
+
*/
|
|
65
|
+
async function pinAndWrite(threadId, analysisId) {
|
|
66
|
+
const pinned = await pinReportSnapshot(pool, analysisId);
|
|
67
|
+
if (pinned.isErr()) {
|
|
68
|
+
log.error("the snapshot pin failed", { threadId, analysisId, ...log.errorFields(pinned.error.cause) });
|
|
69
|
+
return { outcome: "failed", kind: "unavailable", detail: "the artifact ledger read failed" };
|
|
70
|
+
}
|
|
71
|
+
const written = await store.writeSnapshot({ threadId, analysisId, snapshot: pinned.value });
|
|
72
|
+
return written.match((row) => ({ outcome: "ready", state: row }), (error) => storeFault(threadId, error));
|
|
73
|
+
}
|
|
74
|
+
async function ensureSessionState(threadId) {
|
|
75
|
+
const read = await store.readState(threadId);
|
|
76
|
+
if (read.isErr()) {
|
|
77
|
+
return storeFault(threadId, read.error);
|
|
78
|
+
}
|
|
79
|
+
if (read.value !== null) {
|
|
80
|
+
// The row exists, thus the anchor holds and the pin stays single.
|
|
81
|
+
return { outcome: "ready", state: read.value };
|
|
82
|
+
}
|
|
83
|
+
const thread = await threads.getThread(threadId);
|
|
84
|
+
if (thread.isErr()) {
|
|
85
|
+
log.error("the thread read failed", { threadId, ...log.errorFields(thread.error.cause) });
|
|
86
|
+
return { outcome: "failed", kind: "unavailable", detail: describeDbError(thread.error) };
|
|
87
|
+
}
|
|
88
|
+
if (thread.value === null) {
|
|
89
|
+
// The pin needs the analysis of the thread. An absent thread names none, and
|
|
90
|
+
// the absence is a permanent condition.
|
|
91
|
+
log.error("the thread resolves to no analysis", { threadId });
|
|
92
|
+
return { outcome: "failed", kind: "absent-thread", detail: "the thread names no analysis" };
|
|
93
|
+
}
|
|
94
|
+
if (thread.value.threadType !== "report") {
|
|
95
|
+
// The session anchors a report thread only. A conversation thread carries no report session,
|
|
96
|
+
// thus a wrong type writes no row and the detail names the type. The wrong type is permanent.
|
|
97
|
+
log.error("the thread is not a report thread", { threadId, threadType: thread.value.threadType });
|
|
98
|
+
return { outcome: "failed", kind: "wrong-thread-type", detail: `the thread is a ${thread.value.threadType} thread, not a report thread` };
|
|
99
|
+
}
|
|
100
|
+
return pinAndWrite(threadId, thread.value.analysisId);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Map the durable row onto the gateway load. A fresh row holds the snapshot and
|
|
104
|
+
* no document, thus the load gives the empty draft. The found load carries the
|
|
105
|
+
* stored analysis and the prior document as the concurrency token. A row with no
|
|
106
|
+
* snapshot cannot serve a tool, because the pin writes the snapshot first.
|
|
107
|
+
*/
|
|
108
|
+
function toLoad(state) {
|
|
109
|
+
if (state.snapshot === null) {
|
|
110
|
+
log.error("the session-state row holds no snapshot", { threadId: state.threadId });
|
|
111
|
+
return { outcome: "failed", detail: "the session state holds no snapshot" };
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
outcome: "found",
|
|
115
|
+
state: { document: state.document ?? EMPTY_DRAFT, snapshot: state.snapshot },
|
|
116
|
+
analysisId: state.analysisId,
|
|
117
|
+
token: state.document,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const gateway = {
|
|
121
|
+
async load(threadId) {
|
|
122
|
+
const ensured = await ensureSessionState(threadId);
|
|
123
|
+
if (ensured.outcome === "failed") {
|
|
124
|
+
switch (ensured.kind) {
|
|
125
|
+
case "absent-thread":
|
|
126
|
+
return { outcome: "absent" };
|
|
127
|
+
case "wrong-thread-type":
|
|
128
|
+
return { outcome: "wrong-type", detail: ensured.detail };
|
|
129
|
+
case "unavailable":
|
|
130
|
+
return { outcome: "failed", detail: ensured.detail };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return toLoad(ensured.state);
|
|
134
|
+
},
|
|
135
|
+
async persist(threadId, document, expected) {
|
|
136
|
+
const result = await store.persistDocument({ threadId, document, expected });
|
|
137
|
+
return result.match((outcome) => {
|
|
138
|
+
switch (outcome) {
|
|
139
|
+
case "persisted":
|
|
140
|
+
return { outcome: "persisted" };
|
|
141
|
+
case "conflict":
|
|
142
|
+
// A concurrent turn landed first. The turn refuses and reads the state again.
|
|
143
|
+
return { outcome: "conflict" };
|
|
144
|
+
case "absent":
|
|
145
|
+
// The pin writes the row first. A persist that finds no row means the
|
|
146
|
+
// pin-first invariant broke.
|
|
147
|
+
log.error("the session-state persist matched no row", { threadId });
|
|
148
|
+
return { outcome: "failed", detail: "no report session state row exists to persist the document" };
|
|
149
|
+
}
|
|
150
|
+
}, (error) => {
|
|
151
|
+
log.error("the session-state persist failed", { threadId, ...log.errorFields(error.cause) });
|
|
152
|
+
return { outcome: "failed", detail: describeDbError(error) };
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
return { gateway, ensureSessionState };
|
|
157
|
+
}
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* row and the thread reads as it did; a listing widened with `includeArchived`
|
|
23
23
|
* is the only way to obtain the id it takes, so the recovery is reachable by a
|
|
24
24
|
* host that holds no thread ids it obtained elsewhere.
|
|
25
|
-
* `purgeThread` is not recoverable: it removes the `messages` rows
|
|
26
|
-
* metadata rows of the whole subtree in one
|
|
27
|
-
* leaves
|
|
28
|
-
* with nothing naming it. It is the thread-scoped member of this package's
|
|
25
|
+
* `purgeThread` is not recoverable: it removes the `messages` rows, the report
|
|
26
|
+
* session-state rows, and the metadata rows of the whole subtree in one
|
|
27
|
+
* transaction, so a failure partway leaves them all — never a thread stripped of
|
|
28
|
+
* its transcript, nor a transcript with nothing naming it. It is the thread-scoped member of this package's
|
|
29
29
|
* reclamation vocabulary — `purgeAnalysis` reclaims an analysis's whole
|
|
30
30
|
* persisted footprint, `purgeThread` one subtree's.
|
|
31
31
|
*
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* row and the thread reads as it did; a listing widened with `includeArchived`
|
|
23
23
|
* is the only way to obtain the id it takes, so the recovery is reachable by a
|
|
24
24
|
* host that holds no thread ids it obtained elsewhere.
|
|
25
|
-
* `purgeThread` is not recoverable: it removes the `messages` rows
|
|
26
|
-
* metadata rows of the whole subtree in one
|
|
27
|
-
* leaves
|
|
28
|
-
* with nothing naming it. It is the thread-scoped member of this package's
|
|
25
|
+
* `purgeThread` is not recoverable: it removes the `messages` rows, the report
|
|
26
|
+
* session-state rows, and the metadata rows of the whole subtree in one
|
|
27
|
+
* transaction, so a failure partway leaves them all — never a thread stripped of
|
|
28
|
+
* its transcript, nor a transcript with nothing naming it. It is the thread-scoped member of this package's
|
|
29
29
|
* reclamation vocabulary — `purgeAnalysis` reclaims an analysis's whole
|
|
30
30
|
* persisted footprint, `purgeThread` one subtree's.
|
|
31
31
|
*
|
|
@@ -262,10 +262,10 @@ export function createThreadStore(pool) {
|
|
|
262
262
|
WHERE thread_id = $1 AND deleted_at IS NOT NULL`, [threadId])).map(() => undefined);
|
|
263
263
|
}
|
|
264
264
|
function purgeThread(threadId) {
|
|
265
|
-
// One transaction for
|
|
265
|
+
// One transaction for every statement: `messages` is attributable to an
|
|
266
266
|
// analysis only by joining through `cortex_analysis_threads`, so a metadata
|
|
267
267
|
// row removed without its messages strands them beyond the reach of any
|
|
268
|
-
// later reclamation. Either the
|
|
268
|
+
// later reclamation. Either the whole set goes or none of it does.
|
|
269
269
|
//
|
|
270
270
|
// The explicit message delete must reach the same depth the database
|
|
271
271
|
// cascade reaches. The cascade on `parent_thread_id` removes a
|
|
@@ -274,11 +274,20 @@ export function createThreadStore(pool) {
|
|
|
274
274
|
// would strand a grandchild's transcript exactly as a bare cascade strands
|
|
275
275
|
// a child's.
|
|
276
276
|
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
277
|
+
// The session state of a report thread goes the same way, and for the same
|
|
278
|
+
// reason: `cortex_report_session_state` carries no foreign key to
|
|
279
|
+
// `cortex_analysis_threads` either, so the cascade cannot reach it. Its row
|
|
280
|
+
// holds a whole draft document, and a purged thread can never be composed
|
|
281
|
+
// again, so a row left behind is dead weight until the analysis purge runs.
|
|
282
|
+
//
|
|
283
|
+
// Messages go first, and the thread rows go last: the walk every statement
|
|
284
|
+
// shares reads the very rows the last one removes. Thus a thread delete before
|
|
285
|
+
// the others leaves the subtree unresolvable before a later statement names its
|
|
286
|
+
// transcripts and its drafts.
|
|
280
287
|
return withTransaction(pool, "thread-store.purgeThread", (client) => tryMutation("thread-store.purgeThread.messages", () => client.query(`${SUBTREE_CTE}
|
|
281
288
|
DELETE FROM messages WHERE thread_id IN (SELECT thread_id FROM subtree)`, [threadId]))
|
|
289
|
+
.andThen(() => tryMutation("thread-store.purgeThread.reportSessionState", () => client.query(`${SUBTREE_CTE}
|
|
290
|
+
DELETE FROM cortex_report_session_state WHERE thread_id IN (SELECT thread_id FROM subtree)`, [threadId])))
|
|
282
291
|
.andThen(() => tryMutation("thread-store.purgeThread.thread", () => client.query(`${SUBTREE_CTE}
|
|
283
292
|
DELETE FROM cortex_analysis_threads WHERE thread_id IN (SELECT thread_id FROM subtree)`, [threadId])))
|
|
284
293
|
.map(() => undefined));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const reportSessionPrompt = "# Report Builder\n\nYou compose a scientific report for the user, one block at a time, and you ground\nevery claim in the analysis. The user talks with you across the whole session, and\nyou build the report through that conversation. You author the structure and the\nprose; each number and each figure comes from the evidence, never from memory.\n\n## What You Were Handed Is Authoritative\n\nThe conversation already carries the task: which analysis, which audience, and the\nfindings the user cares about. Start from what is in front of you. Reach for a read\ntool only where the context is thin, and reach in a targeted way:\n\n- `inspect_data_profile` \u2014 what the dataset is: its design, its organism, its\n per-file types. Read it when you must state a fact about the data itself.\n- `inspect_run` \u2014 what a run produced, and where its outputs are. Read it when you\n must ground a claim in a run's results.\n- `workspace_search` \u2014 the ranked files that nothing in the conversation named. It\n gives descriptions and metadata, not contents.\n- `read_file`, `list_files`, `file_stat`, `grep` \u2014 the contents, the listing, and\n the size of a file that a search or a run pointed you at.\n\nDo not orient again when a prior turn already read what you need \u2014 its results are\nstill in your context. To reach further is targeted, not a fresh sweep.\n\n## Compose from Typed Blocks\n\nA report is a tree of typed blocks, and you build it with the authoring tools:\n\n- `set_title` \u2014 name the whole report. A draft starts with none, and it needs one.\n- `add_block` \u2014 add one section, or one atom inside a section. You choose the id,\n and it is unique in the draft. A section holds atoms. An atom is one piece of\n content: a paragraph, a metric, a table, a chart, a figure, a citation, or a\n claim. The block schema of `add_block` gives every kind and its fields.\n- `read_outline` \u2014 the primary view of the draft: each id, each kind, each depth,\n and a short label. Read the outline, not the whole draft, to see where you are.\n- `read_block` \u2014 one block in full, when its outline label is not enough.\n\nBuild the report as a shape first, then fill each section. Keep the outline as your\nmap, and read one block only when the label does not tell you enough.\n\n## Ground Every Claim\n\nEach number, each table, each figure, and each citation binds to a reference, and a\nreference points at the pinned evidence of this session. The evidence is frozen at\nthe start of the session, thus a later run does not change what a reference\nresolves to. Bind every value. Never transcribe a number that you remember.\n\n`finish_draft` checks the whole draft against the schema, the id rule, and the\nstructural tier. It returns each completeness gap, or the finished document. Read\nthe gaps, repair the draft, and finish again.\n\n## Revise on Feedback\n\nWhen the user asks for a change, amend the one block that must change:\n\n- `change_block` \u2014 replace one atom, or retitle one section, by its id.\n- `move_block` \u2014 move one block to a new place by its id.\n- `remove_block` \u2014 remove one block, and its subtree, by its id.\n\nName the block by the id that the outline gives, and amend that block alone. Do not\nrebuild the report to make one change.\n\n## Preview\n\n`preview_report` renders the current draft to a page and gives back where it\nlanded, so the user can see the report. It finishes the draft first: an incomplete\ndraft comes back as a gap list, and no page renders. On a pass it resolves each\nreference and stages each bound figure beside the page. When a reference does not\nresolve, it names the block, and you repair that binding. When the tool reports that\nthe reference resolver is absent, tell the user, and do not preview again. Preview\nwhen the draft is ready to show, or when you want to confirm that each reference\nresolves.\n\n## Do NOT\n\n- **Transcribe a number from memory.** Every metric, every table cell, and every\n figure binds to a reference that resolves against the pinned evidence.\n- **Start a run, or change the analysis.** You read the analysis; you never run it\n and never write to it. You hold no tool that does either, and that is by design.\n- **Invent a path.** Name a file by what a search or a run gave you. Never guess a\n location, and never hardcode one.\n- **Reach outside the pinned evidence.** A reference binds to the frozen snapshot\n of this session alone. Do not cite an artifact that the snapshot does not hold.\n- **Rebuild when one amend serves.** One feedback is one block change. Change, move,\n or remove that block by its id, and do not re-author the report.\n- **Leave a gap unread.** When `finish_draft` or `preview_report` reports a gap or\n an unresolved reference, repair it. Do not present a report that does not finish.\n";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export const reportSessionPrompt = `# Report Builder
|
|
2
|
+
|
|
3
|
+
You compose a scientific report for the user, one block at a time, and you ground
|
|
4
|
+
every claim in the analysis. The user talks with you across the whole session, and
|
|
5
|
+
you build the report through that conversation. You author the structure and the
|
|
6
|
+
prose; each number and each figure comes from the evidence, never from memory.
|
|
7
|
+
|
|
8
|
+
## What You Were Handed Is Authoritative
|
|
9
|
+
|
|
10
|
+
The conversation already carries the task: which analysis, which audience, and the
|
|
11
|
+
findings the user cares about. Start from what is in front of you. Reach for a read
|
|
12
|
+
tool only where the context is thin, and reach in a targeted way:
|
|
13
|
+
|
|
14
|
+
- \`inspect_data_profile\` — what the dataset is: its design, its organism, its
|
|
15
|
+
per-file types. Read it when you must state a fact about the data itself.
|
|
16
|
+
- \`inspect_run\` — what a run produced, and where its outputs are. Read it when you
|
|
17
|
+
must ground a claim in a run's results.
|
|
18
|
+
- \`workspace_search\` — the ranked files that nothing in the conversation named. It
|
|
19
|
+
gives descriptions and metadata, not contents.
|
|
20
|
+
- \`read_file\`, \`list_files\`, \`file_stat\`, \`grep\` — the contents, the listing, and
|
|
21
|
+
the size of a file that a search or a run pointed you at.
|
|
22
|
+
|
|
23
|
+
Do not orient again when a prior turn already read what you need — its results are
|
|
24
|
+
still in your context. To reach further is targeted, not a fresh sweep.
|
|
25
|
+
|
|
26
|
+
## Compose from Typed Blocks
|
|
27
|
+
|
|
28
|
+
A report is a tree of typed blocks, and you build it with the authoring tools:
|
|
29
|
+
|
|
30
|
+
- \`set_title\` — name the whole report. A draft starts with none, and it needs one.
|
|
31
|
+
- \`add_block\` — add one section, or one atom inside a section. You choose the id,
|
|
32
|
+
and it is unique in the draft. A section holds atoms. An atom is one piece of
|
|
33
|
+
content: a paragraph, a metric, a table, a chart, a figure, a citation, or a
|
|
34
|
+
claim. The block schema of \`add_block\` gives every kind and its fields.
|
|
35
|
+
- \`read_outline\` — the primary view of the draft: each id, each kind, each depth,
|
|
36
|
+
and a short label. Read the outline, not the whole draft, to see where you are.
|
|
37
|
+
- \`read_block\` — one block in full, when its outline label is not enough.
|
|
38
|
+
|
|
39
|
+
Build the report as a shape first, then fill each section. Keep the outline as your
|
|
40
|
+
map, and read one block only when the label does not tell you enough.
|
|
41
|
+
|
|
42
|
+
## Ground Every Claim
|
|
43
|
+
|
|
44
|
+
Each number, each table, each figure, and each citation binds to a reference, and a
|
|
45
|
+
reference points at the pinned evidence of this session. The evidence is frozen at
|
|
46
|
+
the start of the session, thus a later run does not change what a reference
|
|
47
|
+
resolves to. Bind every value. Never transcribe a number that you remember.
|
|
48
|
+
|
|
49
|
+
\`finish_draft\` checks the whole draft against the schema, the id rule, and the
|
|
50
|
+
structural tier. It returns each completeness gap, or the finished document. Read
|
|
51
|
+
the gaps, repair the draft, and finish again.
|
|
52
|
+
|
|
53
|
+
## Revise on Feedback
|
|
54
|
+
|
|
55
|
+
When the user asks for a change, amend the one block that must change:
|
|
56
|
+
|
|
57
|
+
- \`change_block\` — replace one atom, or retitle one section, by its id.
|
|
58
|
+
- \`move_block\` — move one block to a new place by its id.
|
|
59
|
+
- \`remove_block\` — remove one block, and its subtree, by its id.
|
|
60
|
+
|
|
61
|
+
Name the block by the id that the outline gives, and amend that block alone. Do not
|
|
62
|
+
rebuild the report to make one change.
|
|
63
|
+
|
|
64
|
+
## Preview
|
|
65
|
+
|
|
66
|
+
\`preview_report\` renders the current draft to a page and gives back where it
|
|
67
|
+
landed, so the user can see the report. It finishes the draft first: an incomplete
|
|
68
|
+
draft comes back as a gap list, and no page renders. On a pass it resolves each
|
|
69
|
+
reference and stages each bound figure beside the page. When a reference does not
|
|
70
|
+
resolve, it names the block, and you repair that binding. When the tool reports that
|
|
71
|
+
the reference resolver is absent, tell the user, and do not preview again. Preview
|
|
72
|
+
when the draft is ready to show, or when you want to confirm that each reference
|
|
73
|
+
resolves.
|
|
74
|
+
|
|
75
|
+
## Do NOT
|
|
76
|
+
|
|
77
|
+
- **Transcribe a number from memory.** Every metric, every table cell, and every
|
|
78
|
+
figure binds to a reference that resolves against the pinned evidence.
|
|
79
|
+
- **Start a run, or change the analysis.** You read the analysis; you never run it
|
|
80
|
+
and never write to it. You hold no tool that does either, and that is by design.
|
|
81
|
+
- **Invent a path.** Name a file by what a search or a run gave you. Never guess a
|
|
82
|
+
location, and never hardcode one.
|
|
83
|
+
- **Reach outside the pinned evidence.** A reference binds to the frozen snapshot
|
|
84
|
+
of this session alone. Do not cite an artifact that the snapshot does not hold.
|
|
85
|
+
- **Rebuild when one amend serves.** One feedback is one block change. Change, move,
|
|
86
|
+
or remove that block by its id, and do not re-author the report.
|
|
87
|
+
- **Leave a gap unread.** When \`finish_draft\` or \`preview_report\` reports a gap or
|
|
88
|
+
an unresolved reference, repair it. Do not present a report that does not finish.
|
|
89
|
+
`;
|
|
@@ -32,6 +32,16 @@ export interface AiSdkProviderDeps {
|
|
|
32
32
|
readonly logger?: Logger;
|
|
33
33
|
/** Output-token ceiling per request. Defaults to {@link DEFAULT_MAX_OUTPUT_TOKENS}. */
|
|
34
34
|
readonly maxOutputTokens?: number;
|
|
35
|
+
/** The retry limit of the harness envelope. Defaults to {@link RETRY_MAX_RETRIES}. */
|
|
36
|
+
readonly maxRetries?: number;
|
|
37
|
+
/**
|
|
38
|
+
* The bound for each silent interval of a request attempt, in milliseconds.
|
|
39
|
+
* The provider hands the value to the SDK as the bound for each gap between
|
|
40
|
+
* two content chunks of a stream. The value also names itself in the error of
|
|
41
|
+
* a stream that the bound stopped. When the field is absent, the provider
|
|
42
|
+
* hands the SDK no bound.
|
|
43
|
+
*/
|
|
44
|
+
readonly requestTimeoutMs?: number;
|
|
35
45
|
}
|
|
36
46
|
/**
|
|
37
47
|
* Connection + model configuration for one `ChatProvider`, discriminated by
|
|
@@ -52,6 +62,27 @@ export type AiSdkProviderConfig = {
|
|
|
52
62
|
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
53
63
|
/** Output-token ceiling per request. Defaults to {@link DEFAULT_MAX_OUTPUT_TOKENS}. */
|
|
54
64
|
readonly maxOutputTokens?: number;
|
|
65
|
+
/**
|
|
66
|
+
* The bound for each silent interval of a request attempt, in
|
|
67
|
+
* milliseconds. The value bounds the wait until the response starts, and
|
|
68
|
+
* each gap between two content chunks of a stream, per attempt. A chunk
|
|
69
|
+
* that carries no content, for example a keep-alive comment, does not
|
|
70
|
+
* reset the gap bound. When the field is absent, the provider installs no
|
|
71
|
+
* guard, and its behavior does not change.
|
|
72
|
+
*
|
|
73
|
+
* The harness lifts the transport floor of Bun: the guard adds
|
|
74
|
+
* `timeout: false` to the fetch init, thus the idle cut of 300 seconds
|
|
75
|
+
* does not apply. Under Node the floor of undici stays. Thus a Node
|
|
76
|
+
* embedder that sets a value above 300 seconds supplies a `fetch` whose
|
|
77
|
+
* dispatcher raises that limit.
|
|
78
|
+
*/
|
|
79
|
+
readonly requestTimeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* The retry limit of the harness envelope. The envelope uses this value
|
|
82
|
+
* in place of the default limit. When the field is absent, the envelope
|
|
83
|
+
* keeps the default of 10.
|
|
84
|
+
*/
|
|
85
|
+
readonly maxRetries?: number;
|
|
55
86
|
} | {
|
|
56
87
|
readonly kind: "openai-compatible";
|
|
57
88
|
readonly name: string;
|
|
@@ -62,6 +93,27 @@ export type AiSdkProviderConfig = {
|
|
|
62
93
|
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
63
94
|
/** Output-token ceiling per request. Defaults to {@link DEFAULT_MAX_OUTPUT_TOKENS}. */
|
|
64
95
|
readonly maxOutputTokens?: number;
|
|
96
|
+
/**
|
|
97
|
+
* The bound for each silent interval of a request attempt, in
|
|
98
|
+
* milliseconds. The value bounds the wait until the response starts, and
|
|
99
|
+
* each gap between two content chunks of a stream, per attempt. A chunk
|
|
100
|
+
* that carries no content, for example a keep-alive comment, does not
|
|
101
|
+
* reset the gap bound. When the field is absent, the provider installs no
|
|
102
|
+
* guard, and its behavior does not change.
|
|
103
|
+
*
|
|
104
|
+
* The harness lifts the transport floor of Bun: the guard adds
|
|
105
|
+
* `timeout: false` to the fetch init, thus the idle cut of 300 seconds
|
|
106
|
+
* does not apply. Under Node the floor of undici stays. Thus a Node
|
|
107
|
+
* embedder that sets a value above 300 seconds supplies a `fetch` whose
|
|
108
|
+
* dispatcher raises that limit.
|
|
109
|
+
*/
|
|
110
|
+
readonly requestTimeoutMs?: number;
|
|
111
|
+
/**
|
|
112
|
+
* The retry limit of the harness envelope. The envelope uses this value
|
|
113
|
+
* in place of the default limit. When the field is absent, the envelope
|
|
114
|
+
* keeps the default of 10.
|
|
115
|
+
*/
|
|
116
|
+
readonly maxRetries?: number;
|
|
65
117
|
};
|
|
66
118
|
export interface ConfiguredAiSdkProviderDeps {
|
|
67
119
|
readonly config: AiSdkProviderConfig;
|
|
@@ -78,11 +130,36 @@ export interface ConfiguredAiSdkProviderDeps {
|
|
|
78
130
|
*/
|
|
79
131
|
export declare function computeRetryDelayMs(error: unknown, exponentialBackoffDelay: number, random?: () => number): number;
|
|
80
132
|
export declare function createAiSdkProvider(deps: AiSdkProviderDeps): ChatProvider;
|
|
133
|
+
/**
|
|
134
|
+
* Wrap a fetch so that the wait until the response starts does not exceed
|
|
135
|
+
* `requestTimeoutMs`. The wrapper arms one timer for each fetch call. The timer
|
|
136
|
+
* runs from the send until the fetch promise settles, and the headers of the
|
|
137
|
+
* response settle it. On expiry the wrapper aborts a dedicated controller with a
|
|
138
|
+
* `RequestTimeoutError` reason, and it composes that controller with the caller
|
|
139
|
+
* signal through `AbortSignal.any`. Thus the caller signal keeps its normal
|
|
140
|
+
* effect, and a caller abort stays a cancellation.
|
|
141
|
+
*
|
|
142
|
+
* The guard bounds the response start only. The SDK bounds each later gap of a
|
|
143
|
+
* streamed body with its own `chunkMs` setting, thus no timer of the wrapper
|
|
144
|
+
* survives the headers. Thus a stream with steady content does not trip the
|
|
145
|
+
* guard, whatever its total length. One fetch call is one attempt, because the
|
|
146
|
+
* SDK retries are off, thus the window is per attempt.
|
|
147
|
+
*
|
|
148
|
+
* When the field is absent, `createConfiguredAiSdkProvider` installs no wrapper,
|
|
149
|
+
* and the behavior stays byte-identical.
|
|
150
|
+
*/
|
|
151
|
+
export declare function wrapFetchWithRequestTimeout(fetchImpl: FetchLike, requestTimeoutMs: number): FetchLike;
|
|
81
152
|
/**
|
|
82
153
|
* Realize an `AiSdkProviderConfig` into a `ChatProvider` bound to that config's
|
|
83
154
|
* connection and model. The model is closed into the returned provider here (it
|
|
84
155
|
* is never passed per `ChatRequest`), so N models require N provider
|
|
85
156
|
* instances over one shared connection configuration — one instance serves the
|
|
86
157
|
* single model it was built with.
|
|
158
|
+
*
|
|
159
|
+
* When the config sets `requestTimeoutMs`, the provider wraps the effective fetch
|
|
160
|
+
* (`config.fetch ?? globalThis.fetch`) with the response-start guard, it hands
|
|
161
|
+
* the value to the stream call as the SDK chunk bound, and it advertises the
|
|
162
|
+
* value on the returned instance. When the field is absent, it installs no
|
|
163
|
+
* wrapper, it hands the SDK no bound, and it advertises no value.
|
|
87
164
|
*/
|
|
88
165
|
export declare function createConfiguredAiSdkProvider(deps: ConfiguredAiSdkProviderDeps): ChatProvider;
|