dsh-ptc-plus 0.4.3 → 0.4.10
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/README.en.md +15 -3
- package/README.md +15 -3
- package/client.js +1157 -533
- package/cordis.patch.yml +12 -2
- package/docs/README.md +2 -0
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +60 -4
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +5 -1
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +82 -18
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
- package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
- package/docs/architecture.md +6 -4
- package/docs/binding-review-placement-design.md +29 -21
- package/docs/client-ui.md +20 -8
- package/docs/client-visual-contract.md +48 -0
- package/docs/durability-design.md +12 -0
- package/docs/installation.md +48 -9
- package/docs/publishing.md +4 -4
- package/docs/release-notes.md +109 -0
- package/docs/runtime-reference.md +8 -2
- package/docs/user-bindings.en.md +2 -2
- package/docs/user-bindings.md +3 -3
- package/docs/verification-optimization-work.md +26 -0
- package/docs/verification.md +7 -1
- package/index.js +28 -7
- package/internal/compiler-platform.js +5 -3
- package/internal/compiler-service.js +2 -9
- package/internal/cordis-tools-owner.js +75 -16
- package/internal/direct-surface-owner.js +333 -7
- package/internal/edit-transport-owner.js +8 -5
- package/internal/execution-provider-owner.js +157 -0
- package/internal/execution-seam-compat.js +69 -113
- package/internal/isolated-worker.js +12 -8
- package/internal/recovery-tips.js +50 -21
- package/internal/rejected-cell-editor.js +11 -21
- package/internal/runtime-bridge-owner.js +88 -57
- package/internal/runtime-contexts.js +13 -13
- package/internal/session-events.js +40 -4
- package/internal/session-journal-recovery.js +678 -181
- package/internal/session-log-view.js +189 -43
- package/internal/session-runtime.js +58 -10
- package/internal/settings-compat.js +12 -2
- package/internal/user-bindings-owner.js +6 -5
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +18 -13
- package/src/client-authoring-view.js +222 -141
- package/src/client-binding-review.js +2 -0
- package/src/client-code-editor.js +6 -0
- package/src/client-copy.js +10 -0
- package/src/client-feature-gates.js +12 -5
- package/src/client-host-compat.js +23 -2
- package/src/client-primitives.js +249 -0
- package/src/client-repl-view.js +3 -3
- package/src/client-settings-view.js +2 -29
- package/src/client-styles.js +92 -33
- package/src/client-tool-view.js +8 -3
- package/src/client-workbench.js +9 -7
- package/src/client.js +72 -46
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Own PTC direct presentation, prompt projection, and stream normalization. */
|
|
2
|
+
import { boundContextSummary, createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
3
|
+
import { validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
|
|
4
|
+
import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
|
|
2
5
|
import { canonicalizeToolCallStream } from './tool-call-canonicalizer.js'
|
|
3
6
|
import { editRunCodeSchema } from './rejected-cell-editor.js'
|
|
4
7
|
import { sessionRuntimeContexts } from './runtime-contexts.js'
|
|
@@ -19,6 +22,12 @@ const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the bo
|
|
|
19
22
|
const RUN_CODE_DESCRIPTION_DESCRIPTION = 'Short active-voice summary of what this cell does, 5-10 words (shown in the UI).'
|
|
20
23
|
const CODE_TRANSPORT_INSTRUCTION = '`run_code` and `edit_run_code` are the only tools callable directly. Call every native tool declared by the SDK from inside a program.'
|
|
21
24
|
const PTC_COLLAPSE_SECTION_NAMES = Object.freeze(['tools:ptc-only', 'tools:code-only'])
|
|
25
|
+
const INVALID_TOOL_JSON_FAILURE = Object.freeze({
|
|
26
|
+
code: 'MALFORMED_RESPONSE',
|
|
27
|
+
message: 'DeepSeek Messages stream: tool input is invalid JSON',
|
|
28
|
+
})
|
|
29
|
+
const MALFORMED_TOOL_REJECTION_ID = 'dsh-ptc-plus-malformed-tool-json'
|
|
30
|
+
const MALFORMED_TOOL_REJECTION_ARGUMENTS = '{"dsh_ptc_plus_malformed_tool_json":'
|
|
22
31
|
|
|
23
32
|
function adaptRunCodeSchema(tool) {
|
|
24
33
|
const parameters = tool.parameters
|
|
@@ -139,6 +148,314 @@ async function* bindCallPolicies(source, policy, calls) {
|
|
|
139
148
|
}
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
function invalidToolJsonFinish(chunk) {
|
|
152
|
+
const failure = chunk?.reason?.failure
|
|
153
|
+
return chunk?.type === 'finish'
|
|
154
|
+
&& chunk.reason.kind === 'error'
|
|
155
|
+
&& failure?.code === INVALID_TOOL_JSON_FAILURE.code
|
|
156
|
+
&& failure?.message === INVALID_TOOL_JSON_FAILURE.message
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function invalidToolJsonError(error) {
|
|
160
|
+
const failure = error?.failure ?? error
|
|
161
|
+
return failure?.code === INVALID_TOOL_JSON_FAILURE.code
|
|
162
|
+
&& failure?.message === INVALID_TOOL_JSON_FAILURE.message
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function directToolSchemas(tools) {
|
|
166
|
+
const schemas = new Map()
|
|
167
|
+
if (!Array.isArray(tools)) return schemas
|
|
168
|
+
for (const tool of tools) {
|
|
169
|
+
if (tool?.name !== RUN_CODE && tool?.name !== EDIT_RUN_CODE) continue
|
|
170
|
+
if (schemas.has(tool.name) || !isRecord(tool.parameters)) return new Map()
|
|
171
|
+
schemas.set(tool.name, tool.parameters)
|
|
172
|
+
}
|
|
173
|
+
return schemas
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function repairableDirectCall(chunks, tools) {
|
|
177
|
+
const finish = chunks.at(-1)
|
|
178
|
+
if (!invalidToolJsonFinish(finish)) return undefined
|
|
179
|
+
const completed = completedToolBlocks(chunks)
|
|
180
|
+
if (completed?.length !== 1) return undefined
|
|
181
|
+
const body = chunks.slice(0, -1)
|
|
182
|
+
const starts = body.filter(chunk => chunk?.type === 'block-start' && chunk.blockType === 'tool-call')
|
|
183
|
+
const deltas = body.filter(chunk => chunk?.type === 'tool-call-delta')
|
|
184
|
+
const ends = body.filter(chunk => chunk?.type === 'block-end' && chunk.block?.type === 'tool-call')
|
|
185
|
+
const usages = body.filter(chunk => chunk?.type === 'usage')
|
|
186
|
+
if (starts.length !== 1 || deltas.length === 0 || ends.length !== 1
|
|
187
|
+
|| usages.length > 1
|
|
188
|
+
|| body.some(chunk => chunk?.type !== 'usage' && !toolStreamChunk(chunk))) {
|
|
189
|
+
return undefined
|
|
190
|
+
}
|
|
191
|
+
const index = starts[0].index
|
|
192
|
+
const end = ends[0]
|
|
193
|
+
const id = end.block.id
|
|
194
|
+
const name = end.block.name
|
|
195
|
+
if (!Number.isSafeInteger(index) || index < 0 || typeof id !== 'string' || id.length === 0
|
|
196
|
+
|| (name !== RUN_CODE && name !== EDIT_RUN_CODE)
|
|
197
|
+
|| end.index !== index
|
|
198
|
+
|| typeof end.block.arguments !== 'string'
|
|
199
|
+
|| deltas.some(chunk => chunk.index !== index || chunk.id !== id
|
|
200
|
+
|| (chunk.name !== undefined && chunk.name !== name)
|
|
201
|
+
|| typeof chunk.argumentsDelta !== 'string')) {
|
|
202
|
+
return undefined
|
|
203
|
+
}
|
|
204
|
+
const raw = deltas.map(chunk => chunk.argumentsDelta).join('')
|
|
205
|
+
if (raw !== end.block.arguments || !raw.endsWith('}')) return undefined
|
|
206
|
+
const candidate = raw.slice(0, -1)
|
|
207
|
+
let value
|
|
208
|
+
try {
|
|
209
|
+
value = JSON.parse(candidate)
|
|
210
|
+
} catch {
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
const schema = directToolSchemas(tools).get(name)
|
|
214
|
+
if (!isRecord(value) || schema === undefined
|
|
215
|
+
|| validateJsonSchemaValue(schema, value).length !== 0) return undefined
|
|
216
|
+
return { index, candidate }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function canonicalToolBlock(index, block) {
|
|
220
|
+
return [
|
|
221
|
+
{ type: 'block-start', index, blockType: 'tool-call' },
|
|
222
|
+
{
|
|
223
|
+
type: 'tool-call-delta', index, id: block.id, name: block.name,
|
|
224
|
+
argumentsDelta: block.arguments,
|
|
225
|
+
},
|
|
226
|
+
{ type: 'block-end', index, block },
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function repairedDirectChunks(chunks, repair, usageAlreadyEmitted) {
|
|
231
|
+
const end = chunks.find(chunk => chunk?.type === 'block-end'
|
|
232
|
+
&& chunk.index === repair.index && chunk.block?.type === 'tool-call')
|
|
233
|
+
const block = { ...end.block, arguments: repair.candidate }
|
|
234
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
235
|
+
return [
|
|
236
|
+
...canonicalToolBlock(repair.index, block),
|
|
237
|
+
...usage === undefined ? [] : [usage],
|
|
238
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function completedToolBlocks(chunks) {
|
|
243
|
+
const body = chunks.slice(0, -1)
|
|
244
|
+
if (body.filter(chunk => chunk?.type === 'usage').length > 1
|
|
245
|
+
|| body.some(chunk => chunk?.type !== 'usage' && !toolStreamChunk(chunk))) {
|
|
246
|
+
return undefined
|
|
247
|
+
}
|
|
248
|
+
const calls = new Map()
|
|
249
|
+
for (const chunk of chunks) {
|
|
250
|
+
if (chunk?.type === 'block-start' && chunk.blockType === 'tool-call') {
|
|
251
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
252
|
+
const call = calls.get(chunk.index)
|
|
253
|
+
if (call?.started === true || call?.block !== undefined) return undefined
|
|
254
|
+
if (call === undefined) calls.set(chunk.index, {
|
|
255
|
+
arguments: '', hasDelta: false, started: true,
|
|
256
|
+
})
|
|
257
|
+
else call.started = true
|
|
258
|
+
} else if (chunk?.type === 'tool-call-delta') {
|
|
259
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
260
|
+
let call = calls.get(chunk.index)
|
|
261
|
+
if (call === undefined) {
|
|
262
|
+
call = { arguments: '', hasDelta: false, started: false }
|
|
263
|
+
calls.set(chunk.index, call)
|
|
264
|
+
}
|
|
265
|
+
if (call.block !== undefined
|
|
266
|
+
|| typeof chunk.id !== 'string' || chunk.id.length === 0
|
|
267
|
+
|| (call.id !== undefined && call.id !== chunk.id)
|
|
268
|
+
|| (chunk.name !== undefined && (typeof chunk.name !== 'string' || chunk.name.length === 0
|
|
269
|
+
|| (call.name !== undefined && call.name !== chunk.name)))
|
|
270
|
+
|| typeof chunk.argumentsDelta !== 'string') return undefined
|
|
271
|
+
call.id ??= chunk.id
|
|
272
|
+
if (chunk.name !== undefined) call.name ??= chunk.name
|
|
273
|
+
call.hasDelta = true
|
|
274
|
+
call.arguments += chunk.argumentsDelta
|
|
275
|
+
} else if (chunk?.type === 'block-end' && chunk.block?.type === 'tool-call') {
|
|
276
|
+
if (!Number.isSafeInteger(chunk.index) || chunk.index < 0) return undefined
|
|
277
|
+
let call = calls.get(chunk.index)
|
|
278
|
+
if (call === undefined) {
|
|
279
|
+
call = { arguments: '', hasDelta: false, started: false }
|
|
280
|
+
calls.set(chunk.index, call)
|
|
281
|
+
}
|
|
282
|
+
if (call.block !== undefined
|
|
283
|
+
|| typeof chunk.block.id !== 'string' || chunk.block.id.length === 0
|
|
284
|
+
|| typeof chunk.block.name !== 'string' || chunk.block.name.length === 0
|
|
285
|
+
|| typeof chunk.block.arguments !== 'string'
|
|
286
|
+
|| (call.id !== undefined && call.id !== chunk.block.id)
|
|
287
|
+
|| (call.name !== undefined && call.name !== chunk.block.name)
|
|
288
|
+
|| (call.hasDelta && call.arguments !== chunk.block.arguments)) return undefined
|
|
289
|
+
call.block = chunk.block
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (calls.size === 0 || [...calls.values()].some(call => call.block === undefined)) {
|
|
293
|
+
return undefined
|
|
294
|
+
}
|
|
295
|
+
const completed = [...calls].map(([index, call]) => ({ index, block: call.block }))
|
|
296
|
+
if (new Set(completed.map(call => call.block.id)).size !== completed.length) return undefined
|
|
297
|
+
return completed
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function firstUsageChunk(chunks) {
|
|
301
|
+
return chunks.find(chunk => chunk?.type === 'usage')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function settleMalformedToolCalls(chunks, priorIndexes, usageAlreadyEmitted) {
|
|
305
|
+
if (!invalidToolJsonFinish(chunks.at(-1))) return undefined
|
|
306
|
+
const calls = completedToolBlocks(chunks)
|
|
307
|
+
if (calls === undefined || calls.some(call => priorIndexes.has(call.index))) return undefined
|
|
308
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
309
|
+
return [
|
|
310
|
+
...calls.flatMap(call => canonicalToolBlock(call.index, call.block)),
|
|
311
|
+
...usage === undefined ? [] : [usage],
|
|
312
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function toolStreamChunk(chunk) {
|
|
317
|
+
return chunk?.type === 'tool-call-delta'
|
|
318
|
+
|| (chunk?.type === 'block-start' && chunk.blockType === 'tool-call')
|
|
319
|
+
|| (chunk?.type === 'block-end' && chunk.block?.type === 'tool-call')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function unusedString(base, used) {
|
|
323
|
+
let value = base
|
|
324
|
+
for (let suffix = 2; used.has(value); suffix++) value = `${base}-${suffix}`
|
|
325
|
+
return value
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function visibleBlockClosures(openBlocks, chunks) {
|
|
329
|
+
const closing = new Map([...openBlocks].map(([index, block]) => [index, { ...block }]))
|
|
330
|
+
for (const chunk of chunks) {
|
|
331
|
+
const block = closing.get(chunk?.index)
|
|
332
|
+
if (block === undefined) continue
|
|
333
|
+
if (chunk.type === `${block.type}-delta` && typeof chunk.text === 'string') {
|
|
334
|
+
block.text += chunk.text
|
|
335
|
+
} else if (chunk.type === 'block-end' && chunk.block?.type === block.type) {
|
|
336
|
+
block.final = chunk.block
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return [...closing].map(([index, block]) => ({
|
|
340
|
+
type: 'block-end',
|
|
341
|
+
index,
|
|
342
|
+
block: block.final ?? { type: block.type, text: block.text },
|
|
343
|
+
}))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function rejectedMalformedToolStream(
|
|
347
|
+
chunks,
|
|
348
|
+
priorIndexes = new Set(),
|
|
349
|
+
openBlocks = new Map(),
|
|
350
|
+
usageAlreadyEmitted = false,
|
|
351
|
+
) {
|
|
352
|
+
const usedIndexes = new Set(priorIndexes)
|
|
353
|
+
const usedIds = new Set()
|
|
354
|
+
for (const chunk of chunks) {
|
|
355
|
+
if (Number.isSafeInteger(chunk?.index) && chunk.index >= 0) usedIndexes.add(chunk.index)
|
|
356
|
+
if (typeof chunk?.id === 'string') usedIds.add(chunk.id)
|
|
357
|
+
if (typeof chunk?.block?.id === 'string') usedIds.add(chunk.block.id)
|
|
358
|
+
}
|
|
359
|
+
let index = 0
|
|
360
|
+
while (usedIndexes.has(index)) index++
|
|
361
|
+
const id = unusedString(MALFORMED_TOOL_REJECTION_ID, usedIds)
|
|
362
|
+
const usage = usageAlreadyEmitted ? undefined : firstUsageChunk(chunks)
|
|
363
|
+
return [
|
|
364
|
+
...visibleBlockClosures(openBlocks, chunks),
|
|
365
|
+
{ type: 'block-start', index, blockType: 'tool-call' },
|
|
366
|
+
{
|
|
367
|
+
type: 'tool-call-delta', index, id, name: RUN_CODE,
|
|
368
|
+
argumentsDelta: MALFORMED_TOOL_REJECTION_ARGUMENTS,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
type: 'block-end', index,
|
|
372
|
+
block: {
|
|
373
|
+
type: 'tool-call', id, name: RUN_CODE,
|
|
374
|
+
arguments: MALFORMED_TOOL_REJECTION_ARGUMENTS,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
...usage === undefined ? [] : [usage],
|
|
378
|
+
{ type: 'finish', reason: { kind: 'tool-calls' } },
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function recoveredMalformedChunks(chunks, tools, priorIndexes, openBlocks, usageAlreadyEmitted) {
|
|
383
|
+
if (usageAlreadyEmitted && firstUsageChunk(chunks) !== undefined) {
|
|
384
|
+
return rejectedMalformedToolStream(chunks, priorIndexes, openBlocks, true)
|
|
385
|
+
}
|
|
386
|
+
const repair = repairableDirectCall(chunks, tools)
|
|
387
|
+
const settled = repair !== undefined && !priorIndexes.has(repair.index)
|
|
388
|
+
? repairedDirectChunks(chunks, repair, usageAlreadyEmitted)
|
|
389
|
+
: settleMalformedToolCalls(chunks, priorIndexes, usageAlreadyEmitted)
|
|
390
|
+
return settled === undefined
|
|
391
|
+
? rejectedMalformedToolStream(chunks, priorIndexes, openBlocks, usageAlreadyEmitted)
|
|
392
|
+
: [...visibleBlockClosures(openBlocks, chunks), ...settled]
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function observeVisibleBlock(openBlocks, chunk) {
|
|
396
|
+
if (chunk?.type === 'block-start'
|
|
397
|
+
&& (chunk.blockType === 'text' || chunk.blockType === 'reasoning')) {
|
|
398
|
+
openBlocks.set(chunk.index, { type: chunk.blockType, text: '' })
|
|
399
|
+
} else if (chunk?.type === 'text-delta' || chunk?.type === 'reasoning-delta') {
|
|
400
|
+
const block = openBlocks.get(chunk.index)
|
|
401
|
+
const type = chunk.type === 'text-delta' ? 'text' : 'reasoning'
|
|
402
|
+
if (block?.type === type) block.text += chunk.text
|
|
403
|
+
} else if (chunk?.type === 'block-end') {
|
|
404
|
+
openBlocks.delete(chunk.index)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function* recoverMalformedToolCallStream(source, tools) {
|
|
409
|
+
let pending = []
|
|
410
|
+
const priorIndexes = new Set()
|
|
411
|
+
const openBlocks = new Map()
|
|
412
|
+
let usageSeen = false
|
|
413
|
+
try {
|
|
414
|
+
for await (const chunk of source) {
|
|
415
|
+
if (pending.length === 0) {
|
|
416
|
+
if (toolStreamChunk(chunk)) pending.push(chunk)
|
|
417
|
+
else if (chunk?.type === 'usage' && usageSeen) pending.push(chunk)
|
|
418
|
+
else if (invalidToolJsonFinish(chunk)) {
|
|
419
|
+
yield* rejectedMalformedToolStream([chunk], priorIndexes, openBlocks, usageSeen)
|
|
420
|
+
openBlocks.clear()
|
|
421
|
+
} else {
|
|
422
|
+
if (Number.isSafeInteger(chunk?.index) && chunk.index >= 0) priorIndexes.add(chunk.index)
|
|
423
|
+
observeVisibleBlock(openBlocks, chunk)
|
|
424
|
+
if (chunk?.type === 'usage') usageSeen = true
|
|
425
|
+
yield chunk
|
|
426
|
+
}
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
pending.push(chunk)
|
|
430
|
+
if (chunk?.type !== 'finish') continue
|
|
431
|
+
const output = invalidToolJsonFinish(chunk)
|
|
432
|
+
? recoveredMalformedChunks(pending, tools, priorIndexes, openBlocks, usageSeen)
|
|
433
|
+
: pending
|
|
434
|
+
for (const outputChunk of output) {
|
|
435
|
+
if (Number.isSafeInteger(outputChunk?.index) && outputChunk.index >= 0) {
|
|
436
|
+
priorIndexes.add(outputChunk.index)
|
|
437
|
+
}
|
|
438
|
+
if (outputChunk?.type === 'usage') usageSeen = true
|
|
439
|
+
yield outputChunk
|
|
440
|
+
}
|
|
441
|
+
if (invalidToolJsonFinish(chunk)) openBlocks.clear()
|
|
442
|
+
pending = []
|
|
443
|
+
}
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (invalidToolJsonError(error)) {
|
|
446
|
+
yield* recoveredMalformedChunks([
|
|
447
|
+
...pending,
|
|
448
|
+
{ type: 'finish', reason: { kind: 'error', failure: INVALID_TOOL_JSON_FAILURE } },
|
|
449
|
+
], tools, priorIndexes, openBlocks, usageSeen)
|
|
450
|
+
return
|
|
451
|
+
} else {
|
|
452
|
+
yield* pending
|
|
453
|
+
throw error
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
yield* pending
|
|
457
|
+
}
|
|
458
|
+
|
|
142
459
|
export function createDirectSurfaceOwner({
|
|
143
460
|
editTransport,
|
|
144
461
|
runtimeConfig,
|
|
@@ -147,6 +464,7 @@ export function createDirectSurfaceOwner({
|
|
|
147
464
|
toolSchemasForAgent,
|
|
148
465
|
userBindingsForAgent = async () => undefined,
|
|
149
466
|
setAgentPresentation = async () => {},
|
|
467
|
+
sessionLogView,
|
|
150
468
|
}) {
|
|
151
469
|
// Composition is anchored to Agent identity because DSH selects presentation
|
|
152
470
|
// once per composed agent. Request signals bind the exact assembly to stream
|
|
@@ -317,6 +635,7 @@ export function createDirectSurfaceOwner({
|
|
|
317
635
|
}
|
|
318
636
|
const runtimeContexts = sessionPtc
|
|
319
637
|
? sessionRuntimeContexts(agent, tipConfig, {
|
|
638
|
+
sessionLogView,
|
|
320
639
|
cordisRecoveryRequired(view) {
|
|
321
640
|
return cordisRecovery.required(agent, view)
|
|
322
641
|
},
|
|
@@ -383,7 +702,8 @@ export function createDirectSurfaceOwner({
|
|
|
383
702
|
editToolName: EDIT_RUN_CODE,
|
|
384
703
|
})
|
|
385
704
|
: next()
|
|
386
|
-
|
|
705
|
+
const recovered = recoverMalformedToolCallStream(source, options.tools)
|
|
706
|
+
return bindCallPolicies(recovered, policy, policy.owner.calls)
|
|
387
707
|
},
|
|
388
708
|
executionRejection(exec) {
|
|
389
709
|
if (exec.name === EDIT_RUN_CODE && exec.parent !== undefined) {
|
|
@@ -430,12 +750,18 @@ export function createDirectSurfaceOwner({
|
|
|
430
750
|
const outer = missingPath === 'description'
|
|
431
751
|
&& policy?.autoDescribeRunCode !== true
|
|
432
752
|
&& !(isRecord(exec.arguments) && typeof exec.arguments.description === 'string')
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
753
|
+
const text = outer
|
|
754
|
+
? 'The run_code outer transport arguments are invalid at JSON path $.description. Add a sibling string `description` field to run_code; a description nested inside a native-tool argument does not satisfy this outer requirement.'
|
|
755
|
+
: `A nested native-tool argument is missing required property "${missingPath}". Add a string description at that path in the native-tool arguments; the outer run_code description does not satisfy this nested requirement.`
|
|
756
|
+
// additionalContexts enter the durable inbox as messages, not prompt sections.
|
|
757
|
+
const context = createUserMessage({
|
|
758
|
+
source: {
|
|
759
|
+
kind: PTC_MESSAGE_SOURCE_KIND,
|
|
760
|
+
form: 'notice',
|
|
761
|
+
summary: boundContextSummary('tools:ptc-plus-run-code-arguments'),
|
|
762
|
+
},
|
|
763
|
+
content: [{ type: 'text', text }],
|
|
764
|
+
})
|
|
439
765
|
return {
|
|
440
766
|
...diagnosed,
|
|
441
767
|
additionalContexts: [...diagnosed.additionalContexts ?? [], context],
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
editRejectedCell,
|
|
18
18
|
editRunCodeSchema,
|
|
19
19
|
} from './rejected-cell-editor.js'
|
|
20
|
-
import {
|
|
20
|
+
import { projectSessionLog } from './session-log-view.js'
|
|
21
21
|
import { RUN_CODE } from './runtime-bridge-owner.js'
|
|
22
22
|
import { isRecord } from './record-utils.js'
|
|
23
23
|
import {
|
|
@@ -67,6 +67,8 @@ export function createEditTransportOwner(ctx, {
|
|
|
67
67
|
presentationGeneration,
|
|
68
68
|
sessionId,
|
|
69
69
|
toolSchemasForAgent,
|
|
70
|
+
readSessionLog,
|
|
71
|
+
sessionLogView = projectSessionLog,
|
|
70
72
|
}) {
|
|
71
73
|
let currentDurableReplay = durableReplay
|
|
72
74
|
const editExecutionMetadata = new WeakMap()
|
|
@@ -143,13 +145,14 @@ export function createEditTransportOwner(ctx, {
|
|
|
143
145
|
definition.execute = async (args, exec) => {
|
|
144
146
|
const agent = exec?.agent
|
|
145
147
|
const callId = typeof exec?.callId === 'string' ? exec.callId : undefined
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
+
const sessionLog = await readSessionLog(agent?.session, exec.signal)
|
|
149
|
+
const persistedTargetCallSeq = liveToolCallSeq(sessionLog, callId, EDIT_RUN_CODE)
|
|
150
|
+
if (persistedTargetCallSeq === undefined && sessionLogView(agent).editableRun !== undefined) {
|
|
148
151
|
throw new Error('ptc-plus: edit_run_code requires a unique persisted tool/call event')
|
|
149
152
|
}
|
|
150
153
|
const target = persistedTargetCallSeq === undefined
|
|
151
154
|
? undefined
|
|
152
|
-
:
|
|
155
|
+
: sessionLogView(agent, { callId, callSeq: persistedTargetCallSeq }).requestedEditTarget
|
|
153
156
|
if (target?.source === undefined || target.callSeq === undefined) {
|
|
154
157
|
return { edited: false, reason: 'no run_code cell is currently eligible for safe editing' }
|
|
155
158
|
}
|
|
@@ -190,7 +193,7 @@ export function createEditTransportOwner(ctx, {
|
|
|
190
193
|
agent,
|
|
191
194
|
signal: exec.signal,
|
|
192
195
|
})
|
|
193
|
-
const tentative = await executeTentative(persistedCallSeq, dispatch)
|
|
196
|
+
const tentative = await executeTentative(persistedCallSeq, dispatch, sessionLog)
|
|
194
197
|
const inner = tentative.result
|
|
195
198
|
finalizeTentative = tentative.finalize
|
|
196
199
|
const journalValue = isRecord(inner?.meta) && Object.hasOwn(inner.meta, JOURNAL_KEY)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { createExecutionSeam, EXECUTION_SEAM_SERVICE } from './execution-seam-compat.js'
|
|
2
|
+
|
|
3
|
+
export const name = 'ptc-plus-execution-provider'
|
|
4
|
+
export const inject = ['loader']
|
|
5
|
+
|
|
6
|
+
export function apply(ctx, config = {}) {
|
|
7
|
+
const ownerFiber = ctx.fiber
|
|
8
|
+
const { originalEntryId = 'ptc-runtime', isolation = 'ptc-plus-original' } = config
|
|
9
|
+
if (typeof originalEntryId !== 'string' || originalEntryId.length === 0
|
|
10
|
+
|| typeof isolation !== 'string' || isolation.length === 0) {
|
|
11
|
+
throw new TypeError('ptc-plus: execution provider requires an original entry id and named isolation')
|
|
12
|
+
}
|
|
13
|
+
// Profile rows belong to the Include's tree; the global Loader resolves root IDs.
|
|
14
|
+
const entryTree = ownerFiber.entry?.parent.tree ?? ctx.loader
|
|
15
|
+
const originalEntry = entryTree.resolve(originalEntryId)
|
|
16
|
+
const isolated = () => originalEntry.options.isolate?.[EXECUTION_SEAM_SERVICE] === isolation
|
|
17
|
+
if (!isolated()) throw new Error(`ptc-plus: original entry ${originalEntryId} must isolate ${EXECUTION_SEAM_SERVICE} as ${isolation}`)
|
|
18
|
+
const originalContext = originalEntry.context.extend({ [originalEntry.constructor.key]: undefined })
|
|
19
|
+
let watcher
|
|
20
|
+
let live
|
|
21
|
+
let stopped = false
|
|
22
|
+
let suspended = false
|
|
23
|
+
let generation
|
|
24
|
+
const pending = new Set()
|
|
25
|
+
let resolveAttachment
|
|
26
|
+
let rejectAttachment
|
|
27
|
+
const attachment = new Promise((resolve, reject) => {
|
|
28
|
+
resolveAttachment = resolve
|
|
29
|
+
rejectAttachment = reject
|
|
30
|
+
})
|
|
31
|
+
const readOriginal = () => {
|
|
32
|
+
const original = isolated() ? originalContext.get(EXECUTION_SEAM_SERVICE) : undefined
|
|
33
|
+
if (stopped || original === undefined) {
|
|
34
|
+
throw new Error(`ptc-plus: original ${EXECUTION_SEAM_SERVICE} generation is unavailable`)
|
|
35
|
+
}
|
|
36
|
+
return original
|
|
37
|
+
}
|
|
38
|
+
const release = () => {
|
|
39
|
+
const entry = live
|
|
40
|
+
if (entry === undefined) return
|
|
41
|
+
live = undefined
|
|
42
|
+
entry.seam.retire()
|
|
43
|
+
const unregistration = entry.unregister?.()
|
|
44
|
+
const disposal = Promise.allSettled([unregistration, entry.disposeChild?.()]).then(results => {
|
|
45
|
+
const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
|
|
46
|
+
if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus: execution provider disposal failed')
|
|
47
|
+
})
|
|
48
|
+
pending.add(disposal)
|
|
49
|
+
disposal.then(() => pending.delete(disposal), error => {
|
|
50
|
+
pending.delete(disposal)
|
|
51
|
+
ctx.logger.warn(error)
|
|
52
|
+
})
|
|
53
|
+
return disposal
|
|
54
|
+
}
|
|
55
|
+
const suspend = () => {
|
|
56
|
+
suspended = true
|
|
57
|
+
const disposal = release()
|
|
58
|
+
const previous = watcher
|
|
59
|
+
watcher = undefined
|
|
60
|
+
return Promise.all([disposal, previous?.dispose()])
|
|
61
|
+
}
|
|
62
|
+
const publish = () => {
|
|
63
|
+
const resolutions = new WeakMap()
|
|
64
|
+
const delegate = {
|
|
65
|
+
get language() { return readOriginal().language },
|
|
66
|
+
get isolation() { return readOriginal().isolation },
|
|
67
|
+
get executionInstructions() { return readOriginal().executionInstructions },
|
|
68
|
+
get sandboxMode() { return readOriginal().sandboxMode },
|
|
69
|
+
get timeout() { return readOriginal().timeout },
|
|
70
|
+
resolve(request) {
|
|
71
|
+
const original = readOriginal()
|
|
72
|
+
const spec = original.resolve(request)
|
|
73
|
+
if (spec !== null && typeof spec === 'object') {
|
|
74
|
+
resolutions.set(spec, { original, generation })
|
|
75
|
+
}
|
|
76
|
+
return spec
|
|
77
|
+
},
|
|
78
|
+
run(spec) {
|
|
79
|
+
const resolved = resolutions.get(spec)
|
|
80
|
+
if (resolved?.generation?.active === false) throw new Error('ptc-plus: resolved spec belongs to an unavailable original generation')
|
|
81
|
+
const original = resolved?.original ?? readOriginal()
|
|
82
|
+
return original.run(spec)
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
const seam = createExecutionSeam(delegate, EXECUTION_SEAM_SERVICE)
|
|
86
|
+
const entry = { seam }
|
|
87
|
+
live = entry
|
|
88
|
+
entry.disposeChild = ctx.effect(() => {
|
|
89
|
+
const child = ctx.plugin({
|
|
90
|
+
name: 'ptc-plus-composed-runtime',
|
|
91
|
+
apply(childContext) {
|
|
92
|
+
childContext.effect(() => {
|
|
93
|
+
if (stopped || live !== entry || !isolated()) return () => {}
|
|
94
|
+
entry.unregister = childContext.provide(EXECUTION_SEAM_SERVICE, seam.provider)
|
|
95
|
+
return () => entry.unregister()
|
|
96
|
+
}, 'ptc-plus owned execution registration')
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
entry.child = child
|
|
100
|
+
return () => child.dispose()
|
|
101
|
+
}, 'ptc-plus owned execution child')
|
|
102
|
+
return entry.child.await().then(() => {
|
|
103
|
+
if (!stopped && live === entry && entry.unregister !== undefined && entry.child.state === 2) resolveAttachment()
|
|
104
|
+
}, error => {
|
|
105
|
+
rejectAttachment(error)
|
|
106
|
+
throw error
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
const watch = () => {
|
|
110
|
+
if (stopped || watcher !== undefined || !isolated()) return
|
|
111
|
+
ctx.effect(() => {
|
|
112
|
+
const observer = originalContext.inject([EXECUTION_SEAM_SERVICE], scope => {
|
|
113
|
+
if (stopped || !isolated()) return
|
|
114
|
+
createExecutionSeam(scope.get(EXECUTION_SEAM_SERVICE), EXECUTION_SEAM_SERVICE)
|
|
115
|
+
const currentGeneration = { active: true }
|
|
116
|
+
generation = currentGeneration
|
|
117
|
+
scope.effect(() => () => {
|
|
118
|
+
currentGeneration.active = false
|
|
119
|
+
if (generation === currentGeneration) generation = undefined
|
|
120
|
+
}, 'ptc-plus original generation scope')
|
|
121
|
+
if (live === undefined) return publish()
|
|
122
|
+
return live.seam.sourceChanged()
|
|
123
|
+
})
|
|
124
|
+
watcher = observer
|
|
125
|
+
return () => observer.dispose()
|
|
126
|
+
}, 'ptc-plus original execution observer')
|
|
127
|
+
}
|
|
128
|
+
ctx.on('loader/partial-dispose', (entry, _legacy, active) => {
|
|
129
|
+
if (entry === originalEntry && (!active || !isolated())) void suspend().catch(error => ctx.logger.warn(error))
|
|
130
|
+
}, { prepend: true, global: true })
|
|
131
|
+
ctx.on('loader/patch-context', (entry, next) => {
|
|
132
|
+
if (entry === originalEntry && !isolated()) void suspend().catch(error => ctx.logger.warn(error))
|
|
133
|
+
next()
|
|
134
|
+
if (entry === originalEntry && isolated() && suspended && !stopped && ownerFiber.state === 2) {
|
|
135
|
+
stopped = true
|
|
136
|
+
resolveAttachment()
|
|
137
|
+
void suspend().catch(error => ctx.logger.warn(error))
|
|
138
|
+
void ownerFiber.restart().catch(error => ctx.logger.warn(error))
|
|
139
|
+
}
|
|
140
|
+
}, { prepend: true, global: true })
|
|
141
|
+
ctx.effect(() => () => {
|
|
142
|
+
stopped = true
|
|
143
|
+
resolveAttachment()
|
|
144
|
+
const suspension = suspend()
|
|
145
|
+
return Promise.all([suspension, ...pending])
|
|
146
|
+
}, 'ptc-plus execution provider composition')
|
|
147
|
+
if (originalEntry.disabled) resolveAttachment()
|
|
148
|
+
const timer = setTimeout(() => rejectAttachment(new Error(
|
|
149
|
+
`ptc-plus: original execution entry ${originalEntryId} did not publish ${EXECUTION_SEAM_SERVICE}`,
|
|
150
|
+
)), 30_000)
|
|
151
|
+
ctx.effect(() => () => clearTimeout(timer), 'ptc-plus original execution provider wait')
|
|
152
|
+
const originalReady = originalEntry.fiber?.await() ?? Promise.resolve()
|
|
153
|
+
return originalReady.then(() => {
|
|
154
|
+
watch()
|
|
155
|
+
return attachment
|
|
156
|
+
}).finally(() => clearTimeout(timer))
|
|
157
|
+
}
|