@nylorun/runtime 0.20.0-beta → 0.21.0-beta
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 +64 -0
- package/README.md +15 -10
- package/dist/api/components.d.ts +155 -36
- package/dist/api/components.js +3 -3
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/routes/mcp.d.ts +9 -0
- package/dist/api/http/routes/mcp.js +32 -0
- package/dist/api/http/routes/vaults.d.ts +1 -9
- package/dist/api/http/routes/vaults.js +3 -103
- package/dist/api/openapi.js +6 -3
- package/dist/artifacts/media-types.d.ts +2 -0
- package/dist/artifacts/media-types.js +5 -0
- package/dist/gates/http-tool.d.ts +7 -0
- package/dist/gates/http-tool.js +34 -3
- package/dist/gates/mcp-handler.d.ts +4 -1
- package/dist/gates/mcp-handler.js +64 -8
- package/dist/gates/tenant-vaults.d.ts +9 -15
- package/dist/gates/tenant-vaults.js +9 -17
- package/dist/gates/tool-client.js +36 -17
- package/dist/gates/tool-contract.d.ts +19 -3
- package/dist/gates/tool-contract.js +9 -3
- package/dist/gates/tool-gate.d.ts +2 -2
- package/dist/harness/calls.d.ts +2 -0
- package/dist/harness/calls.js +13 -3
- package/dist/harness-api/record.d.ts +7 -6
- package/dist/harness-api/record.js +99 -17
- package/dist/harness-api/server.js +35 -27
- package/dist/harness-api/start.js +2 -1
- package/dist/host/config-for.d.ts +0 -2
- package/dist/host/config-for.js +0 -1
- package/dist/host/gates.js +0 -1
- package/dist/host/main.js +13 -3
- package/dist/host/stack-config.d.ts +5 -8
- package/dist/host/stack-config.js +8 -19
- package/dist/index.d.ts +0 -1
- package/dist/keys/client.js +1 -9
- package/dist/keys/keys.d.ts +11 -13
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +315 -345
- package/dist/mcp/connect.d.ts +159 -13
- package/dist/mcp/connect.js +538 -55
- package/dist/mcp/pool.d.ts +11 -2
- package/dist/mcp/pool.js +74 -27
- package/dist/mcp/preview.d.ts +35 -0
- package/dist/mcp/preview.js +215 -0
- package/dist/mcp/snapshot.d.ts +79 -5
- package/dist/mcp/snapshot.js +145 -27
- package/dist/model/catalog.d.ts +14 -0
- package/dist/model/catalog.js +14 -1
- package/dist/model/pi-model.js +36 -2
- package/dist/openapi.json +362 -123
- package/dist/redact.d.ts +19 -1
- package/dist/redact.js +42 -13
- package/dist/sandbox/session-sandbox.d.ts +7 -0
- package/dist/sandbox/session-sandbox.js +89 -28
- package/dist/store/postgres/drizzle/0016_mcp_oauth_removed.sql +5 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
- package/dist/store/postgres/schema.d.ts +2 -261
- package/dist/store/postgres/schema.js +2 -36
- package/dist/store/postgres/store.js +1 -18
- package/dist/store/postgres/tenant.d.ts +14 -0
- package/dist/store/postgres/tenant.js +22 -2
- package/dist/store/types.d.ts +2 -8
- package/dist/tenant/artifact-tool.d.ts +9 -1
- package/dist/tenant/artifact-tool.js +113 -9
- package/dist/tenant/context.d.ts +5 -6
- package/dist/tenant/effects.d.ts +2 -2
- package/dist/tenant/effects.js +20 -5
- package/dist/tenant/ephemeral.d.ts +0 -6
- package/dist/tenant/ephemeral.js +0 -1
- package/dist/tenant/outbound.d.ts +14 -5
- package/dist/tenant/outbound.js +21 -9
- package/dist/tenant/runtime.js +9 -14
- package/dist/tenant/sessions.js +20 -3
- package/dist/tenant/store-pg.js +9 -0
- package/dist/tenant/tool-results.d.ts +57 -0
- package/dist/tenant/tool-results.js +298 -0
- package/dist/tenant/tool-search.d.ts +43 -0
- package/dist/tenant/tool-search.js +100 -0
- package/dist/tenant/transcript.d.ts +108 -0
- package/dist/tenant/transcript.js +13 -0
- package/dist/tenant/types.d.ts +0 -14
- package/dist/vault/crypto.d.ts +0 -7
- package/dist/vault/crypto.js +0 -16
- package/dist/vault/headers.d.ts +17 -0
- package/dist/vault/headers.js +61 -0
- package/dist/vault/service.d.ts +11 -68
- package/dist/vault/service.js +83 -438
- package/dist/vault/sources.d.ts +20 -48
- package/dist/vault/sources.js +25 -189
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/vault/oauth.d.ts +0 -55
- package/dist/vault/oauth.js +0 -186
package/dist/mcp/snapshot.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { TOOLS_CAPABILITY_ID, deferredToolsInstructions, deferredToolsTools, delegateManifest, mcpToolSettings, } from "@nylorun/core/define";
|
|
2
3
|
/** The `mcp.discovered` payload: each server's outcome, with the tools it added to the snapshot. */
|
|
3
4
|
export function mcpDiscovered(snapshot, diagnostics) {
|
|
4
5
|
const key = (item) => JSON.stringify([item.agentId ?? null, item.capabilityId, item.serverName]);
|
|
5
6
|
const tools = new Map();
|
|
6
|
-
|
|
7
|
+
const deferred = new Map();
|
|
8
|
+
for (const tool of snapshot.mcpTools) {
|
|
7
9
|
tools.set(key(tool), (tools.get(key(tool)) ?? 0) + 1);
|
|
10
|
+
if (tool.deferred)
|
|
11
|
+
deferred.set(key(tool), (deferred.get(key(tool)) ?? 0) + 1);
|
|
12
|
+
}
|
|
8
13
|
return {
|
|
9
14
|
servers: diagnostics.map((item) => ({
|
|
10
15
|
...(item.agentId === undefined ? {} : { agentId: item.agentId }),
|
|
@@ -13,12 +18,32 @@ export function mcpDiscovered(snapshot, diagnostics) {
|
|
|
13
18
|
outcome: item.outcome,
|
|
14
19
|
message: item.message,
|
|
15
20
|
tools: tools.get(key(item)) ?? 0,
|
|
21
|
+
...(deferred.has(key(item)) ? { deferred: deferred.get(key(item)) } : {}),
|
|
22
|
+
...(item.disabled ? { disabled: item.disabled } : {}),
|
|
23
|
+
...(item.unknownTools?.length ? { unknownTools: [...item.unknownTools] } : {}),
|
|
16
24
|
...(item.credentialIds === undefined ? {} : { credentialIds: [...item.credentialIds] }),
|
|
25
|
+
...(item.renamed?.length ? { renamed: item.renamed.map((tool) => ({ ...tool })) } : {}),
|
|
17
26
|
})),
|
|
18
27
|
};
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
/** The longest tool name every model provider accepts (R2b C6, Q14). */
|
|
30
|
+
export const MODEL_TOOL_NAME_MAX = 64;
|
|
31
|
+
/**
|
|
32
|
+
* The name the model knows an MCP tool by (R2b C6, Q13): `server__tool`, with each character
|
|
33
|
+
* outside `[A-Za-z0-9_-]` replaced by `_`, since MCP allows `.` and 128 characters and OpenAI,
|
|
34
|
+
* Bedrock and Gemini on Vertex do not. A name over 64 characters, or one that collides
|
|
35
|
+
* (`suffixed`), keeps 55 and adds `_` and 8 hex characters of the SHA-256 of the raw
|
|
36
|
+
* `server/tool`.
|
|
37
|
+
*/
|
|
38
|
+
export function normalizeToolName(serverName, serverToolName, options = {}) {
|
|
39
|
+
const name = `${serverName}__${serverToolName}`.replace(/[^A-Za-z0-9_-]/gu, "_");
|
|
40
|
+
if (name.length <= MODEL_TOOL_NAME_MAX && !options.suffixed)
|
|
41
|
+
return name;
|
|
42
|
+
const hash = createHash("sha256")
|
|
43
|
+
.update(`${serverName}/${serverToolName}`)
|
|
44
|
+
.digest("hex")
|
|
45
|
+
.slice(0, 8);
|
|
46
|
+
return `${name.slice(0, MODEL_TOOL_NAME_MAX - 9)}_${hash}`;
|
|
22
47
|
}
|
|
23
48
|
export function declaredToolNames(manifest) {
|
|
24
49
|
const names = new Set();
|
|
@@ -27,37 +52,130 @@ export function declaredToolNames(manifest) {
|
|
|
27
52
|
names.add(tool.name);
|
|
28
53
|
return names;
|
|
29
54
|
}
|
|
55
|
+
/** The declaration of the server a tool of the snapshot comes from, in `manifest`. */
|
|
56
|
+
function serverOf(manifest, tool) {
|
|
57
|
+
const agent = tool.agentId === undefined ? manifest : delegateManifest(manifest, tool.agentId);
|
|
58
|
+
return agent?.capabilities.find((item) => item.id === tool.capabilityId)?.mcpServers?.[tool.serverName];
|
|
59
|
+
}
|
|
60
|
+
/** The tool's settings in `manifest` (R2b C9); the defaults when its server is not there. */
|
|
61
|
+
function settingsOf(manifest, tool) {
|
|
62
|
+
const server = serverOf(manifest, tool);
|
|
63
|
+
return server ? mcpToolSettings(server, tool.serverToolName) : { enabled: true, approval: "never" };
|
|
64
|
+
}
|
|
65
|
+
/** True when the agent the tool belongs to has `tool_search` and `tool_call` (`nylorun.tools`). */
|
|
66
|
+
function reachesDeferred(manifest, agentId) {
|
|
67
|
+
const agent = agentId === undefined ? manifest : delegateManifest(manifest, agentId);
|
|
68
|
+
return agent?.capabilities.some((capability) => capability.id === TOOLS_CAPABILITY_ID) === true;
|
|
69
|
+
}
|
|
30
70
|
/**
|
|
31
|
-
* The tools of a snapshot, as the engine
|
|
32
|
-
*
|
|
71
|
+
* The tools of a snapshot, as the engine gets them for a turn whose manifest is `manifest` (the
|
|
72
|
+
* session's pin, or a turn variant that tightened its MCP servers' tools):
|
|
73
|
+
*
|
|
74
|
+
* - a tool its settings disable is left out (R2b C9); one whose approval resolves to `always`
|
|
75
|
+
* waits for approval on each call;
|
|
76
|
+
* - a deferred tool (R2b C10) is marked so, out of the model's list, and its agent gets
|
|
77
|
+
* `tool_search`, with the note on the servers it holds deferred tools of, and `tool_call` in
|
|
78
|
+
* its `nylorun.tools` capability. An agent whose turn has no such capability keeps its tools in
|
|
79
|
+
* its list.
|
|
80
|
+
*
|
|
81
|
+
* Nothing here changes between the steps of a turn, so neither does the model's tool list.
|
|
33
82
|
*/
|
|
34
83
|
export function sessionToolsOf(snapshot, manifest) {
|
|
35
84
|
if (!snapshot?.mcpTools.length)
|
|
36
85
|
return undefined;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
? {}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
const inline = [];
|
|
87
|
+
const deferred = new Map();
|
|
88
|
+
const notes = new Map();
|
|
89
|
+
for (const tool of snapshot.mcpTools) {
|
|
90
|
+
const settings = settingsOf(manifest, tool);
|
|
91
|
+
if (!settings.enabled)
|
|
92
|
+
continue;
|
|
93
|
+
const defer = tool.deferred === true && reachesDeferred(manifest, tool.agentId);
|
|
94
|
+
const entry = {
|
|
95
|
+
...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
|
|
96
|
+
capabilityId: tool.capabilityId,
|
|
97
|
+
name: tool.name,
|
|
98
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
99
|
+
inputSchema: tool.inputSchema,
|
|
100
|
+
...(tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }),
|
|
101
|
+
...(settings.approval === "always" ? { approval: "always" } : {}),
|
|
102
|
+
...(defer ? { deferred: true } : {}),
|
|
103
|
+
};
|
|
104
|
+
if (!defer) {
|
|
105
|
+
inline.push(entry);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
deferred.set(tool.agentId, [...(deferred.get(tool.agentId) ?? []), entry]);
|
|
109
|
+
const servers = notes.get(tool.agentId) ?? new Map();
|
|
110
|
+
servers.set(tool.serverName, (servers.get(tool.serverName) ?? 0) + 1);
|
|
111
|
+
notes.set(tool.agentId, servers);
|
|
112
|
+
}
|
|
113
|
+
const platform = [];
|
|
114
|
+
for (const [agentId, tools] of deferred) {
|
|
115
|
+
const servers = [...notes.get(agentId)].map(([serverName, count]) => {
|
|
116
|
+
const instructions = snapshot.servers?.find((item) => item.agentId === agentId && item.serverName === serverName)?.instructions;
|
|
117
|
+
return { serverName, tools: count, ...(instructions ? { instructions } : {}) };
|
|
118
|
+
});
|
|
119
|
+
const [search, call] = deferredToolsTools();
|
|
120
|
+
for (const tool of [
|
|
121
|
+
{ ...search, instructions: [deferredToolsInstructions(servers)] },
|
|
122
|
+
call,
|
|
123
|
+
])
|
|
124
|
+
platform.push({
|
|
125
|
+
...(agentId === undefined ? {} : { agentId }),
|
|
126
|
+
capabilityId: TOOLS_CAPABILITY_ID,
|
|
127
|
+
...tool,
|
|
128
|
+
});
|
|
129
|
+
platform.push(...tools);
|
|
130
|
+
}
|
|
131
|
+
return [...inline, ...platform];
|
|
55
132
|
}
|
|
56
|
-
/** The
|
|
57
|
-
export
|
|
58
|
-
|
|
133
|
+
/** The share of the model's context window past which an agent's MCP tools are deferred (Q21). */
|
|
134
|
+
export const DEFER_CONTEXT_SHARE = 0.1;
|
|
135
|
+
/** Characters per token, to estimate what tool definitions take of a context window (Q21). */
|
|
136
|
+
export const CHARS_PER_TOKEN = 4;
|
|
137
|
+
/**
|
|
138
|
+
* The snapshot with its deferred tools marked (R2b C10, Q21), decided once for the session when
|
|
139
|
+
* core records it. A tool's own setting, then `"*"`'s, then its server's, decides; a tool none
|
|
140
|
+
* sets is deferred when the JSON of its agent's MCP tools that would be listed passes a tenth of
|
|
141
|
+
* the model's context window (`contextWindow` tokens, at 4 characters a token). An agent without
|
|
142
|
+
* the `nylorun.tools` capability (its definition names a tool `tool_search` or `tool_call`, or the
|
|
143
|
+
* session was opened before it existed) defers nothing.
|
|
144
|
+
*/
|
|
145
|
+
export function pinDeferral(snapshot, manifest, contextWindow) {
|
|
146
|
+
const limit = contextWindow * CHARS_PER_TOKEN * DEFER_CONTEXT_SHARE;
|
|
147
|
+
const listed = new Map();
|
|
148
|
+
const set = snapshot.mcpTools.map((tool) => settingsOf(manifest, tool).deferred);
|
|
149
|
+
snapshot.mcpTools.forEach((tool, i) => {
|
|
150
|
+
if (set[i] === true)
|
|
151
|
+
return;
|
|
152
|
+
const size = JSON.stringify({ name: tool.name, description: tool.description ?? "", inputSchema: tool.inputSchema }).length;
|
|
153
|
+
listed.set(tool.agentId, (listed.get(tool.agentId) ?? 0) + size);
|
|
154
|
+
});
|
|
155
|
+
const mcpTools = snapshot.mcpTools.map((tool, i) => {
|
|
156
|
+
const { deferred: _, ...rest } = tool;
|
|
157
|
+
if (!reachesDeferred(manifest, tool.agentId))
|
|
158
|
+
return rest;
|
|
159
|
+
const defer = set[i] ?? (listed.get(tool.agentId) ?? 0) > limit;
|
|
160
|
+
return defer ? { ...rest, deferred: true } : rest;
|
|
161
|
+
});
|
|
162
|
+
return { ...snapshot, mcpTools };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. With
|
|
166
|
+
* the turn's `manifest`, a tool its settings disable is not one either (R2b C9).
|
|
167
|
+
*/
|
|
168
|
+
export function mcpToolOf(snapshot, request, manifest) {
|
|
169
|
+
const tool = snapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
|
|
59
170
|
tool.capabilityId === request.capabilityId &&
|
|
60
171
|
tool.name === request.toolName);
|
|
172
|
+
if (tool && manifest && !isEnabledIn(manifest, tool))
|
|
173
|
+
return undefined;
|
|
174
|
+
return tool;
|
|
175
|
+
}
|
|
176
|
+
/** True when the tool's settings in `manifest` leave it enabled (R2b C9). */
|
|
177
|
+
export function isEnabledIn(manifest, tool) {
|
|
178
|
+
return settingsOf(manifest, tool).enabled;
|
|
61
179
|
}
|
|
62
180
|
/** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
|
|
63
181
|
export function manifestFor(manifest, agent) {
|
package/dist/model/catalog.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { HostModelCatalog } from "@nylorun/core/contracts";
|
|
2
2
|
export type { HostModelCatalog };
|
|
3
|
+
/**
|
|
4
|
+
* The context window, in tokens, of the Tenant's model (`GET /v1/tenant/model`'s view): the
|
|
5
|
+
* custom endpoint's setting, else the provider catalog's. A model the catalog does not know, or
|
|
6
|
+
* none configured, is assumed small (`CUSTOM_CONTEXT_WINDOW`), as for a custom endpoint.
|
|
7
|
+
*/
|
|
8
|
+
export declare function contextWindowOf(view: {
|
|
9
|
+
configured: boolean;
|
|
10
|
+
provider?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
settings?: {
|
|
14
|
+
contextWindow?: number;
|
|
15
|
+
};
|
|
16
|
+
}): number;
|
|
3
17
|
/** Public provider and model names. No credentials. */
|
|
4
18
|
export declare function hostModelCatalog(): HostModelCatalog;
|
package/dist/model/catalog.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { modelsFor } from "./models.js";
|
|
1
|
+
import { CUSTOM_CONTEXT_WINDOW, modelsFor } from "./models.js";
|
|
2
2
|
const emptyStore = {
|
|
3
3
|
async read() {
|
|
4
4
|
return undefined;
|
|
@@ -11,6 +11,19 @@ const emptyStore = {
|
|
|
11
11
|
},
|
|
12
12
|
async delete() { },
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* The context window, in tokens, of the Tenant's model (`GET /v1/tenant/model`'s view): the
|
|
16
|
+
* custom endpoint's setting, else the provider catalog's. A model the catalog does not know, or
|
|
17
|
+
* none configured, is assumed small (`CUSTOM_CONTEXT_WINDOW`), as for a custom endpoint.
|
|
18
|
+
*/
|
|
19
|
+
export function contextWindowOf(view) {
|
|
20
|
+
if (!view.configured || !view.provider || !view.model)
|
|
21
|
+
return CUSTOM_CONTEXT_WINDOW;
|
|
22
|
+
if (view.baseUrl)
|
|
23
|
+
return view.settings?.contextWindow ?? CUSTOM_CONTEXT_WINDOW;
|
|
24
|
+
const models = modelsFor({ provider: "", model: "" }, emptyStore, { environment: false });
|
|
25
|
+
return models.getModel(view.provider, view.model)?.contextWindow ?? CUSTOM_CONTEXT_WINDOW;
|
|
26
|
+
}
|
|
14
27
|
/** Public provider and model names. No credentials. */
|
|
15
28
|
export function hostModelCatalog() {
|
|
16
29
|
const models = modelsFor({ provider: "", model: "" }, emptyStore, {
|
package/dist/model/pi-model.js
CHANGED
|
@@ -226,11 +226,45 @@ export function piModel(options = {}) {
|
|
|
226
226
|
async function buildContext(call, selected, options) {
|
|
227
227
|
const messages = [];
|
|
228
228
|
const instructions = [];
|
|
229
|
-
const content = async (parts) => {
|
|
229
|
+
const content = async (parts, toolResult = false) => {
|
|
230
230
|
const result = [];
|
|
231
231
|
for (const part of parts) {
|
|
232
232
|
if (part.type === "text")
|
|
233
233
|
result.push({ type: "text", text: part.text });
|
|
234
|
+
else if (part.type === "media" && toolResult) {
|
|
235
|
+
// An image a tool returned, stored as an artifact (R2b C11): shown to a model that
|
|
236
|
+
// reads images. Otherwise, or when it cannot be read, a note: the call still goes.
|
|
237
|
+
const note = (why) => ({
|
|
238
|
+
type: "text",
|
|
239
|
+
text: `(The tool returned an image, ${essence(part.mediaType)}, that is not shown: ${why}.)`,
|
|
240
|
+
});
|
|
241
|
+
if (!selected.input.includes("image") || !isModelImage(part.mediaType)) {
|
|
242
|
+
result.push(note(selected.input.includes("image")
|
|
243
|
+
? "the model reads PNG, JPEG, GIF and WebP images only"
|
|
244
|
+
: "this model does not accept images"));
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!options.files) {
|
|
248
|
+
result.push(note("files are unavailable"));
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const file = await options.files(part.reference);
|
|
253
|
+
if (!isModelImage(file.mediaType))
|
|
254
|
+
result.push(note(`it is ${essence(file.mediaType)}`));
|
|
255
|
+
else
|
|
256
|
+
result.push({
|
|
257
|
+
type: "image",
|
|
258
|
+
data: Buffer.from(file.bytes).toString("base64"),
|
|
259
|
+
mimeType: essence(file.mediaType),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (!(error instanceof FileUnavailableError))
|
|
264
|
+
throw error;
|
|
265
|
+
result.push(note(error.message.replace(/\.$/u, "")));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
234
268
|
else if (part.type === "media") {
|
|
235
269
|
// A file the message named by artifact (protocol 6): its bytes go only into the
|
|
236
270
|
// provider request built here, never into the transcript or the journal.
|
|
@@ -284,7 +318,7 @@ async function buildContext(call, selected, options) {
|
|
|
284
318
|
toolName: item.toolName,
|
|
285
319
|
isError: item.status !== "completed",
|
|
286
320
|
timestamp: Date.now(),
|
|
287
|
-
content: await content(item.content),
|
|
321
|
+
content: await content(item.content, true),
|
|
288
322
|
});
|
|
289
323
|
}
|
|
290
324
|
else if (item.kind === "message" && item.role === "assistant") {
|