codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.3
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 +15 -0
- package/README.md +27 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +155 -1
- package/contracts/v1/parity-suite.json +96 -1
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
- package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/downloads.d.ts +2 -0
- package/dist/src/client.d.ts +38 -0
- package/dist/src/client.js +1222 -59
- package/dist/src/commands/configuration.js +13 -7
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +26 -3
- package/dist/src/commands/experience.js +35 -0
- package/dist/src/commands/files.js +4 -27
- package/dist/src/commands/modes.js +49 -17
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +627 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +7 -2
- package/dist/src/commands/work.js +31 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +161 -0
- package/dist/src/operations/browser-adapter.js +2023 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1208 -0
- package/dist/src/operations/browser-target.d.ts +103 -0
- package/dist/src/operations/browser-target.js +489 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1150 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1143 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +22 -0
- package/dist/src/operations/index.js +22 -0
- package/dist/src/operations/journal.d.ts +142 -0
- package/dist/src/operations/journal.js +1931 -0
- package/dist/src/operations/production-attachments.d.ts +116 -0
- package/dist/src/operations/production-attachments.js +1018 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1363 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +1073 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +118 -0
- package/dist/src/operations/runtime-adapter.js +786 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +281 -0
- package/dist/src/operations/service.js +2810 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +39 -0
- package/dist/src/runtime/coordinated-page.js +634 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
- package/dist/src/scripts/live-smoke/scenarios.js +110 -18
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +1 -1
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +73 -0
- package/references/python-parity.md +72 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
package/dist/src/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
3
|
import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./commands/artifacts.js";
|
|
2
4
|
import { attachFiles, downloadLatestFile, preflightFiles } from "./commands/files.js";
|
|
3
5
|
import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./commands/project-sources.js";
|
|
@@ -20,11 +22,56 @@ import { responseFromRunResult, responsesCreateArgsToRunInput, unsupportedRespon
|
|
|
20
22
|
import { streamFromRunResult } from "./runner/stream.js";
|
|
21
23
|
import { redactReportValue } from "./safety/report-redaction.js";
|
|
22
24
|
import { explainCommandBlocker } from "./diagnostics/blockers.js";
|
|
25
|
+
import { OperationClient, OperationClientError } from "./operations/client.js";
|
|
26
|
+
import { OperationJournal } from "./operations/journal.js";
|
|
27
|
+
import { OperationService } from "./operations/service.js";
|
|
28
|
+
import { OPERATION_CONTROL_REQUEST_SCHEMA_VERSION, OPERATION_REQUEST_SCHEMA_VERSION } from "./operations/types.js";
|
|
29
|
+
import { createRuntimeEnvSession } from "./runtime/runtime-session.js";
|
|
30
|
+
import { coordinateRuntimeEnv } from "./runtime/coordinated-browser.js";
|
|
31
|
+
import { createChatGPTOperationAdapterFactory, createChatGPTOperationControlAdapterFactory, createChatGPTOperationHandleAdapterFactory } from "./operations/chatgpt-runtime.js";
|
|
23
32
|
export function createChatGPT(options = {}) {
|
|
24
|
-
|
|
33
|
+
// RuntimeEnv remains mutable for the legacy command implementations, but it
|
|
34
|
+
// must never be shared by two public invocations. The session owns only the
|
|
35
|
+
// browser/page/tab snapshot; provider references are copied once and each
|
|
36
|
+
// call receives its own mutable capture for the whole workflow.
|
|
37
|
+
const runtimeEnvironment = runtimeEnv(options);
|
|
38
|
+
const runtime = createRuntimeEnvSession(runtimeEnvironment);
|
|
39
|
+
// The cached OperationClient must remain stable so request-local adapters
|
|
40
|
+
// (including file/output closures) survive a submit -> collect sequence.
|
|
41
|
+
// Its default ChatGPT factories, however, must see the invocation that is
|
|
42
|
+
// currently using them. Async-local scope keeps concurrent calls from
|
|
43
|
+
// replacing one another's browser/page snapshot.
|
|
44
|
+
const operationRuntime = new AsyncLocalStorage();
|
|
45
|
+
// A direct in-process client is one runtime lifetime. Keep its coordinator
|
|
46
|
+
// identity stable across every operation while ensuring separately created
|
|
47
|
+
// clients remain diagnostically distinguishable.
|
|
48
|
+
const operationOwner = Object.freeze({
|
|
49
|
+
backendSessionId: randomUUID()
|
|
50
|
+
});
|
|
25
51
|
const limits = normalizeLimits(options.limits);
|
|
52
|
+
// Keep one promise for the lifetime of this client. Opening the journal is
|
|
53
|
+
// deliberately deferred until an operation is actually requested, while
|
|
54
|
+
// concurrent first calls still converge on one authenticated service/key.
|
|
55
|
+
let operationClientPromise;
|
|
56
|
+
const operationClient = () => {
|
|
57
|
+
operationClientPromise ??= createOperationClientForChatGPT(options, () => operationRuntime.getStore() ?? runtimeEnvironment, operationOwner);
|
|
58
|
+
return operationClientPromise;
|
|
59
|
+
};
|
|
60
|
+
const runOperationInvocation = (callback) => {
|
|
61
|
+
const active = operationRuntime.getStore();
|
|
62
|
+
if (active !== undefined)
|
|
63
|
+
return callback();
|
|
64
|
+
return runtime.run(env => operationRuntime.run(env, callback));
|
|
65
|
+
};
|
|
66
|
+
const operations = Object.freeze({
|
|
67
|
+
submit: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.submit(request, operationOptions))),
|
|
68
|
+
collect: (handle, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.collect(handle, operationOptions))),
|
|
69
|
+
inspect: handle => runOperationInvocation(() => operationClient().then(client => client.inspect(handle))),
|
|
70
|
+
control: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.control(request, operationOptions))),
|
|
71
|
+
run: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.run(request, operationOptions)))
|
|
72
|
+
});
|
|
26
73
|
const runnerRun = ((agent, input, runnerOptions) => {
|
|
27
|
-
const run = () => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting);
|
|
74
|
+
const run = () => runtime.run(env => operationRuntime.run(env, () => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting, options, operations)));
|
|
28
75
|
return runnerOptions?.stream === true ? streamFromRunResult(run) : run();
|
|
29
76
|
});
|
|
30
77
|
const runner = {
|
|
@@ -35,24 +82,36 @@ export function createChatGPT(options = {}) {
|
|
|
35
82
|
agent: config => createChatGPTAgent(config),
|
|
36
83
|
run: runner.run,
|
|
37
84
|
runner,
|
|
85
|
+
operations,
|
|
38
86
|
responses: {
|
|
39
|
-
|
|
87
|
+
// Keep the entire Responses adapter, including its runner invocation,
|
|
88
|
+
// inside one capture. Passing a runner bound to this env avoids a
|
|
89
|
+
// nested session capture for the same public workflow.
|
|
90
|
+
create: args => runtime.run(env => operationRuntime.run(env, () => createResponse(args, (agent, input) => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting, options, operations), env.now)))
|
|
40
91
|
},
|
|
41
|
-
ask: args =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
ask: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
|
|
93
|
+
? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
|
|
94
|
+
: runTransactionalAsk(args, options.defaults, options, operations))),
|
|
95
|
+
askInThread: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
|
|
96
|
+
? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
|
|
97
|
+
: runTransactionalAsk(args, options.defaults, options, operations))),
|
|
98
|
+
askWithFiles: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
|
|
99
|
+
? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
|
|
100
|
+
: runTransactionalAsk(args, options.defaults, options, operations))),
|
|
101
|
+
askAndDownload: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
|
|
102
|
+
? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
|
|
103
|
+
: runTransactionalAsk(args, options.defaults, options, operations))),
|
|
104
|
+
runMessages: args => runtime.run(env => runGuarded(planRunMessages(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))),
|
|
105
|
+
openThread: thread => runtime.run(env => runSequence(planOpenThread(thread), env)),
|
|
106
|
+
readLatest: args => runtime.run(env => readLatest(env, args)),
|
|
107
|
+
copyLatest: args => runtime.run(env => copyResponse(env, args)),
|
|
108
|
+
downloadLatest: args => runtime.run(env => downloadLatestFile(env, args)),
|
|
109
|
+
runPlan: plan => runtime.run(env => runPlanInvocation(plan, env, limits, options.defaults, options.reporting)),
|
|
110
|
+
doctor: args => runtime.run(env => doctor(env, args)),
|
|
111
|
+
createReport: (result, args) => runtime.run(env => createRunReport(env, result, args ?? options.reporting ?? {})),
|
|
53
112
|
explainBlocker: (resultOrBlocker, args) => explainCommandBlocker(resultOrBlocker, args),
|
|
54
113
|
reports: {
|
|
55
|
-
create: (result, args) => createRunReport(env, result, args ?? options.reporting ?? {}),
|
|
114
|
+
create: (result, args) => runtime.run(env => createRunReport(env, result, args ?? options.reporting ?? {})),
|
|
56
115
|
redact: async (value, args) => resultOk(redactReportValue(value, args), {}),
|
|
57
116
|
summarize: async (result, args) => resultOk(redactReportValue(resultSummary(result), args), {})
|
|
58
117
|
},
|
|
@@ -61,69 +120,73 @@ export function createChatGPT(options = {}) {
|
|
|
61
120
|
describe: name => describeCommand(name),
|
|
62
121
|
help: topic => helpText(topic),
|
|
63
122
|
session: {
|
|
64
|
-
bootstrap: args => bootstrap(env, args)
|
|
123
|
+
bootstrap: args => runtime.run(env => bootstrap(env, args))
|
|
65
124
|
},
|
|
66
125
|
experience: {
|
|
67
|
-
detect: args => detectExperience(env, args),
|
|
68
|
-
open: args => openExperience(env, args)
|
|
126
|
+
detect: args => runtime.run(env => detectExperience(env, args)),
|
|
127
|
+
open: args => runtime.run(env => openExperience(env, args))
|
|
69
128
|
},
|
|
70
129
|
configuration: {
|
|
71
|
-
inspect: args => inspectConfiguration(env, args),
|
|
72
|
-
apply: args => applyConfiguration(env, args)
|
|
130
|
+
inspect: args => runtime.run(env => inspectConfiguration(env, args)),
|
|
131
|
+
apply: args => runtime.run(env => applyConfiguration(env, args))
|
|
73
132
|
},
|
|
74
133
|
work: {
|
|
75
|
-
start: args =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
start: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
|
|
135
|
+
? startWork(env, args)
|
|
136
|
+
: runTransactionalWorkStart(args, options.defaults, operations))),
|
|
137
|
+
status: args => runtime.run(env => workStatus(env, args)),
|
|
138
|
+
wait: args => runtime.run(env => waitForWork(env, args)),
|
|
139
|
+
steer: args => runtime.run(env => operationRuntime.run(env, () => hasTransactionalWorkControl(args)
|
|
140
|
+
? runTransactionalWorkSteer(args, operations)
|
|
141
|
+
: steerWork(env, args))),
|
|
142
|
+
readLatest: args => runtime.run(env => readLatestWork(env, args)),
|
|
80
143
|
artifacts: {
|
|
81
|
-
listLatest: args => listLatestArtifacts(env, args),
|
|
82
|
-
wait: args => waitForArtifact(env, args),
|
|
83
|
-
downloadLatest: args => downloadLatestArtifact(env, args)
|
|
144
|
+
listLatest: args => runtime.run(env => listLatestArtifacts(env, args)),
|
|
145
|
+
wait: args => runtime.run(env => waitForArtifact(env, args)),
|
|
146
|
+
downloadLatest: args => runtime.run(env => downloadLatestArtifact(env, args))
|
|
84
147
|
}
|
|
85
148
|
},
|
|
86
149
|
threads: {
|
|
87
|
-
new: args => newThread(env, args),
|
|
88
|
-
search: args => searchThreads(env, args),
|
|
89
|
-
open: args => openThread(env, args)
|
|
150
|
+
new: args => runtime.run(env => newThread(env, args)),
|
|
151
|
+
search: args => runtime.run(env => searchThreads(env, args)),
|
|
152
|
+
open: args => runtime.run(env => openThread(env, args))
|
|
90
153
|
},
|
|
91
154
|
messages: {
|
|
92
|
-
compose: args => composeMessage(env, args),
|
|
93
|
-
submit: args => submitMessage(env, args),
|
|
94
|
-
ask: args => askMessage(env, args),
|
|
95
|
-
wait: args => waitForMessage(env, args),
|
|
96
|
-
readLatest: args => readLatest(env, args),
|
|
97
|
-
status: args => messageStatus(env, args),
|
|
98
|
-
stop: args => stopGeneration(env, args),
|
|
99
|
-
waitAndRead: args => waitAndRead(env, args)
|
|
155
|
+
compose: args => runtime.run(env => composeMessage(env, args)),
|
|
156
|
+
submit: args => runtime.run(env => submitMessage(env, args)),
|
|
157
|
+
ask: args => runtime.run(env => askMessage(env, args)),
|
|
158
|
+
wait: args => runtime.run(env => waitForMessage(env, args)),
|
|
159
|
+
readLatest: args => runtime.run(env => readLatest(env, args)),
|
|
160
|
+
status: args => runtime.run(env => messageStatus(env, args)),
|
|
161
|
+
stop: args => runtime.run(env => stopGeneration(env, args)),
|
|
162
|
+
waitAndRead: args => runtime.run(env => waitAndRead(env, args))
|
|
100
163
|
},
|
|
101
164
|
files: {
|
|
102
|
-
preflight: args => preflightFiles(env, args),
|
|
103
|
-
attach: args => attachFiles(env, args),
|
|
104
|
-
downloadLatest: args => downloadLatestFile(env, args)
|
|
165
|
+
preflight: args => runtime.run(env => preflightFiles(env, args)),
|
|
166
|
+
attach: args => runtime.run(env => attachFiles(env, args)),
|
|
167
|
+
downloadLatest: args => runtime.run(env => downloadLatestFile(env, args))
|
|
105
168
|
},
|
|
106
169
|
projects: {
|
|
107
170
|
sources: {
|
|
108
|
-
list: args => listProjectSources(env, args),
|
|
109
|
-
planAdd: args => buildProjectSourceAddPlan(env, args),
|
|
110
|
-
add: args => addProjectSources(env, args)
|
|
171
|
+
list: args => runtime.run(env => listProjectSources(env, args)),
|
|
172
|
+
planAdd: args => runtime.run(env => buildProjectSourceAddPlan(env, args)),
|
|
173
|
+
add: args => runtime.run(env => addProjectSources(env, args))
|
|
111
174
|
}
|
|
112
175
|
},
|
|
113
176
|
artifacts: {
|
|
114
|
-
listLatest: args => listLatestArtifacts(env, args),
|
|
115
|
-
wait: args => waitForArtifact(env, args),
|
|
116
|
-
downloadLatest: args => downloadLatestArtifact(env, args)
|
|
177
|
+
listLatest: args => runtime.run(env => listLatestArtifacts(env, args)),
|
|
178
|
+
wait: args => runtime.run(env => waitForArtifact(env, args)),
|
|
179
|
+
downloadLatest: args => runtime.run(env => downloadLatestArtifact(env, args))
|
|
117
180
|
},
|
|
118
181
|
modes: {
|
|
119
|
-
set: args => setMode(env, args),
|
|
120
|
-
get: args => getMode(env, args)
|
|
182
|
+
set: args => runtime.run(env => setMode(env, args)),
|
|
183
|
+
get: args => runtime.run(env => getMode(env, args))
|
|
121
184
|
},
|
|
122
185
|
tools: {
|
|
123
|
-
select: args => selectTool(env, args)
|
|
186
|
+
select: args => runtime.run(env => selectTool(env, args))
|
|
124
187
|
},
|
|
125
188
|
response: {
|
|
126
|
-
copy: args => copyResponse(env, args)
|
|
189
|
+
copy: args => runtime.run(env => copyResponse(env, args))
|
|
127
190
|
}
|
|
128
191
|
};
|
|
129
192
|
}
|
|
@@ -277,11 +340,11 @@ function capReportOptions(report, limits) {
|
|
|
277
340
|
maxPreviewChars: Math.min(report.maxPreviewChars ?? limits.maxReportPreviewChars, limits.maxReportPreviewChars)
|
|
278
341
|
};
|
|
279
342
|
}
|
|
280
|
-
async function createResponse(args,
|
|
343
|
+
async function createResponse(args, run, now) {
|
|
281
344
|
const validation = validateResponsesCreateArgs(args);
|
|
282
345
|
const timestamp = now?.() ?? new Date();
|
|
283
346
|
if (!validation.ok) {
|
|
284
|
-
return unsupportedResponse(validation.unsupported, timestamp);
|
|
347
|
+
return unsupportedResponse(validation.unsupported, timestamp, readOperationIdFromUnknown(args));
|
|
285
348
|
}
|
|
286
349
|
const responseArgs = args;
|
|
287
350
|
const agentConfig = {
|
|
@@ -292,21 +355,74 @@ async function createResponse(args, runner, now) {
|
|
|
292
355
|
agentConfig.instructions = responseArgs.instructions;
|
|
293
356
|
}
|
|
294
357
|
const agent = createChatGPTAgent(agentConfig);
|
|
295
|
-
const result = await
|
|
358
|
+
const result = await run(agent, responsesCreateArgsToRunInput(responseArgs));
|
|
296
359
|
return responseFromRunResult(result, now?.() ?? timestamp);
|
|
297
360
|
}
|
|
298
|
-
async function runAgentWorkflow(agent, input, env, limits, defaults, reporting) {
|
|
361
|
+
async function runAgentWorkflow(agent, input, env, limits, defaults, reporting, clientOptions, operations) {
|
|
362
|
+
const requestedOperationId = runnerOperationId(input);
|
|
299
363
|
try {
|
|
300
364
|
const normalized = normalizeRunnerInput(agent, input);
|
|
365
|
+
if (normalized.operationId !== undefined) {
|
|
366
|
+
const result = await runTransactionalRunnerWorkflow(agent, normalized, defaults, clientOptions, operations);
|
|
367
|
+
return toRunResult(agent, result);
|
|
368
|
+
}
|
|
301
369
|
const plan = planAgentWorkflowFromNormalized(agent, normalized, defaults);
|
|
302
370
|
const report = reportOptions(normalized.report ?? agent.defaults.report, reporting);
|
|
303
371
|
const result = await runGuarded(plan, env, limits, report);
|
|
304
372
|
return toRunResult(agent, result);
|
|
305
373
|
}
|
|
306
374
|
catch (error) {
|
|
307
|
-
|
|
375
|
+
const result = requestedOperationId === undefined
|
|
376
|
+
? resultError(error instanceof Error ? error : new Error(String(error)), {})
|
|
377
|
+
: transactionalAskError(requestedOperationId, error);
|
|
378
|
+
return toRunResult(agent, result);
|
|
308
379
|
}
|
|
309
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Runner opt-in adapter. It deliberately funnels through the same
|
|
383
|
+
* transactional ask preparation/result mapping used by the high-level Chat
|
|
384
|
+
* helpers so request identity, file manifests, action journaling, and
|
|
385
|
+
* structured blocker handling cannot drift between surfaces.
|
|
386
|
+
*/
|
|
387
|
+
async function runTransactionalRunnerWorkflow(agent, input, defaults, clientOptions, operations) {
|
|
388
|
+
const operationId = input.operationId;
|
|
389
|
+
if (operationId === undefined) {
|
|
390
|
+
return transactionalAskError("invalid-operation", new Error("Transactional runner input is missing operationId."));
|
|
391
|
+
}
|
|
392
|
+
if (agent.instructionsMode === "visible_setup_message" && hasInstructions(agent)) {
|
|
393
|
+
const unsupported = transactionalUnsupported(operationId, "visible_setup_message requires a separate setup turn and is not supported by one transactional operation.", "agent.instructionsMode");
|
|
394
|
+
return unsupported.ok ? transactionalAskError(operationId, new Error("Invalid transactional runner input.")) : unsupported.result;
|
|
395
|
+
}
|
|
396
|
+
if (input.copy !== undefined && input.copy !== false) {
|
|
397
|
+
const unsupported = transactionalUnsupported(operationId, "copy is not supported by the transactional runner path; use operations.collect and an explicit response action.", "copy");
|
|
398
|
+
return unsupported.ok ? transactionalAskError(operationId, new Error("Invalid transactional runner input.")) : unsupported.result;
|
|
399
|
+
}
|
|
400
|
+
// Match the legacy precedence exactly: input values win, then agent
|
|
401
|
+
// defaults, then client defaults. The transactional mapper performs the
|
|
402
|
+
// same merge for target/configuration/wait/read as the Chat ask surface.
|
|
403
|
+
const effectiveDefaults = {
|
|
404
|
+
...(defaults ?? {}),
|
|
405
|
+
...agent.defaults
|
|
406
|
+
};
|
|
407
|
+
const report = input.report ?? agent.defaults.report;
|
|
408
|
+
const result = await runTransactionalAsk({
|
|
409
|
+
operationId,
|
|
410
|
+
prompt: renderRunnerPrompt(agent, input.prompt),
|
|
411
|
+
...(input.thread === undefined ? {} : { thread: input.thread }),
|
|
412
|
+
...(input.existingTab === undefined ? {} : { existingTab: input.existingTab }),
|
|
413
|
+
...(input.preferExistingTab === undefined ? {} : { preferExistingTab: input.preferExistingTab }),
|
|
414
|
+
...(input.experience === undefined ? {} : { experience: input.experience }),
|
|
415
|
+
...(input.configuration === undefined ? {} : { configuration: input.configuration }),
|
|
416
|
+
...(input.mode === undefined ? {} : { mode: input.mode }),
|
|
417
|
+
...(input.tools.length === 0 ? {} : { tools: input.tools }),
|
|
418
|
+
...(input.files.length === 0 ? {} : { files: input.files }),
|
|
419
|
+
...(input.wait === undefined ? {} : { wait: input.wait }),
|
|
420
|
+
...(input.read === undefined ? {} : { read: input.read }),
|
|
421
|
+
...(report === undefined ? {} : { report }),
|
|
422
|
+
...(input.download === undefined || input.download === false ? {} : { download: input.download })
|
|
423
|
+
}, effectiveDefaults, clientOptions, operations);
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
310
426
|
function planAgentWorkflow(agent, input, defaults = {}) {
|
|
311
427
|
return planAgentWorkflowFromNormalized(agent, normalizeRunnerInput(agent, input), defaults);
|
|
312
428
|
}
|
|
@@ -382,6 +498,8 @@ function normalizeRunnerInput(agent, input) {
|
|
|
382
498
|
tools: args.tools ?? [],
|
|
383
499
|
files: [...collected.files, ...attachments]
|
|
384
500
|
};
|
|
501
|
+
if (args.operationId !== undefined)
|
|
502
|
+
normalized.operationId = args.operationId;
|
|
385
503
|
if (args.thread !== undefined)
|
|
386
504
|
normalized.thread = args.thread;
|
|
387
505
|
if (args.existingTab !== undefined)
|
|
@@ -407,6 +525,9 @@ function normalizeRunnerInput(agent, input) {
|
|
|
407
525
|
}
|
|
408
526
|
return normalized;
|
|
409
527
|
}
|
|
528
|
+
function runnerOperationId(input) {
|
|
529
|
+
return typeof input === "string" ? undefined : input.operationId;
|
|
530
|
+
}
|
|
410
531
|
function collectRunnerInput(input) {
|
|
411
532
|
if (typeof input === "string") {
|
|
412
533
|
return { prompt: input, files: [] };
|
|
@@ -504,7 +625,1013 @@ function runtimeEnv(options) {
|
|
|
504
625
|
env.clipboard = options.clipboard;
|
|
505
626
|
if (options.now !== undefined)
|
|
506
627
|
env.now = options.now;
|
|
507
|
-
|
|
628
|
+
if (options.expectedTabId !== undefined)
|
|
629
|
+
env.expectedTabId = options.expectedTabId;
|
|
630
|
+
// Coordinate only the browser/page snapshot. The runtime session still
|
|
631
|
+
// owns invocation isolation and compatibility mutation semantics for the
|
|
632
|
+
// legacy fields; browser methods themselves use the process-wide actor.
|
|
633
|
+
return coordinateRuntimeEnv(env);
|
|
634
|
+
}
|
|
635
|
+
async function createOperationClientForChatGPT(options, runtimeEnvironment, owner) {
|
|
636
|
+
const operationOptions = options.operations ?? {};
|
|
637
|
+
const journal = await OperationJournal.open(operationOptions.stateRoot === undefined ? {} : { stateRoot: operationOptions.stateRoot });
|
|
638
|
+
const serviceOptions = {
|
|
639
|
+
...(operationOptions.maxCasRetries === undefined ? {} : { maxCasRetries: operationOptions.maxCasRetries }),
|
|
640
|
+
...(options.now === undefined ? {} : { now: () => options.now().getTime() })
|
|
641
|
+
};
|
|
642
|
+
const service = new OperationService(journal, serviceOptions);
|
|
643
|
+
const adapter = operationOptions.adapter ?? unavailableOperationAdapter();
|
|
644
|
+
// Supplying any custom adapter seam is an explicit integration choice. Do
|
|
645
|
+
// not combine one half of a custom provider with the default ChatGPT
|
|
646
|
+
// recovery path. With no customization, however, browser-touching
|
|
647
|
+
// operations work out of the box and remain lazy until target resolution.
|
|
648
|
+
const hasCustomAdapter = operationOptions.adapter !== undefined
|
|
649
|
+
|| operationOptions.adapterFactory !== undefined
|
|
650
|
+
|| operationOptions.handleAdapterFactory !== undefined
|
|
651
|
+
|| operationOptions.controlAdapterFactory !== undefined;
|
|
652
|
+
const evidenceDigest = (domain, material) => {
|
|
653
|
+
// Provider primitives use both the journal's short labels and their own
|
|
654
|
+
// versioned slash-separated domains. Preserve short labels verbatim so
|
|
655
|
+
// service-side identities (notably file manifests) remain identical;
|
|
656
|
+
// envelope provider domains inside one bounded journal namespace rather
|
|
657
|
+
// than allowing the provider label to violate the journal API.
|
|
658
|
+
if (/^[a-z][a-z0-9-]{0,63}$/u.test(domain)) {
|
|
659
|
+
return journal.evidenceDigest(domain, material);
|
|
660
|
+
}
|
|
661
|
+
return journal.evidenceDigest("provider-evidence", { domain, material });
|
|
662
|
+
};
|
|
663
|
+
const adapterFactory = hasCustomAdapter
|
|
664
|
+
? operationOptions.adapterFactory
|
|
665
|
+
: async (context) => createChatGPTOperationAdapterFactory({
|
|
666
|
+
env: runtimeEnvironment(),
|
|
667
|
+
owner,
|
|
668
|
+
evidenceDigest
|
|
669
|
+
})(context);
|
|
670
|
+
const handleAdapterFactory = hasCustomAdapter
|
|
671
|
+
? operationOptions.handleAdapterFactory
|
|
672
|
+
: async (context) => createChatGPTOperationHandleAdapterFactory({
|
|
673
|
+
env: runtimeEnvironment(),
|
|
674
|
+
owner,
|
|
675
|
+
evidenceDigest
|
|
676
|
+
})(context);
|
|
677
|
+
const controlAdapterFactory = hasCustomAdapter
|
|
678
|
+
? operationOptions.controlAdapterFactory
|
|
679
|
+
: async (context) => createChatGPTOperationControlAdapterFactory({
|
|
680
|
+
env: runtimeEnvironment(),
|
|
681
|
+
owner,
|
|
682
|
+
evidenceDigest
|
|
683
|
+
})(context);
|
|
684
|
+
return new OperationClient(service, adapter, {
|
|
685
|
+
...(adapterFactory === undefined ? {} : { adapterFactory }),
|
|
686
|
+
...(handleAdapterFactory === undefined ? {} : { handleAdapterFactory }),
|
|
687
|
+
...(controlAdapterFactory === undefined ? {} : { controlAdapterFactory }),
|
|
688
|
+
...(operationOptions.maxCachedAdapters === undefined ? {} : { maxCachedAdapters: operationOptions.maxCachedAdapters })
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* A conservative placeholder keeps the public facade constructible for
|
|
693
|
+
* browser-free inspection and for callers that provide factories later. It
|
|
694
|
+
* never probes a page, claims capabilities, or synthesizes a successful
|
|
695
|
+
* operation result; all browser-touching paths fail closed.
|
|
696
|
+
*/
|
|
697
|
+
function unavailableOperationAdapter() {
|
|
698
|
+
const unavailable = async () => {
|
|
699
|
+
throw new OperationClientError("adapter_unavailable", "A browser-bound operation adapter is required for this operation.");
|
|
700
|
+
};
|
|
701
|
+
return {
|
|
702
|
+
resolveTarget: unavailable,
|
|
703
|
+
submission: {
|
|
704
|
+
observeStaging: unavailable,
|
|
705
|
+
executeFileHandoffOnce: unavailable,
|
|
706
|
+
observeAttachments: unavailable,
|
|
707
|
+
// Send is deliberately exposed as explicit phases. The placeholder
|
|
708
|
+
// has no browser capability, so every phase fails closed; retaining the
|
|
709
|
+
// legacy method below is source compatibility only and is never used by
|
|
710
|
+
// the transactional coordinator.
|
|
711
|
+
prepareSend: unavailable,
|
|
712
|
+
executePreparedSend: unavailable,
|
|
713
|
+
verifyPreparedSend: unavailable,
|
|
714
|
+
recoverSend: unavailable,
|
|
715
|
+
executeFinalTabTransaction: unavailable
|
|
716
|
+
},
|
|
717
|
+
collector: {
|
|
718
|
+
readContext: unavailable,
|
|
719
|
+
observe: unavailable,
|
|
720
|
+
sleep: unavailable
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const TRANSACTIONAL_OPERATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
725
|
+
async function runTransactionalAsk(args, defaults, clientOptions, operations) {
|
|
726
|
+
const prepared = prepareTransactionalAsk(args, defaults, clientOptions);
|
|
727
|
+
if (!prepared.ok)
|
|
728
|
+
return prepared.result;
|
|
729
|
+
try {
|
|
730
|
+
// Route through the public, stable facade so this path has exactly the
|
|
731
|
+
// same identity/journal semantics as callers using chatgpt.operations.run.
|
|
732
|
+
const run = await operations.run(prepared.value.request, prepared.value.options);
|
|
733
|
+
const handle = await freshestOperationHandle(operations, run.submit.handle);
|
|
734
|
+
return transactionalAskCommandResult(prepared.value, run, handle);
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
// OperationClient and adapter errors are intentionally converted to a
|
|
738
|
+
// bounded structured result. Their native messages may include a path,
|
|
739
|
+
// URL, prompt, or provider-private text and must not cross this boundary.
|
|
740
|
+
return transactionalAskError(args.operationId, error);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Work's public workflow keeps its existing command implementation as the
|
|
745
|
+
* compatibility path. An explicit operation ID opts only this client call
|
|
746
|
+
* into the durable submit/collect protocol; the low-level `startWork` command
|
|
747
|
+
* remains available to sequence callers unchanged.
|
|
748
|
+
*/
|
|
749
|
+
async function runTransactionalWorkStart(args, defaults, operations) {
|
|
750
|
+
const prepared = prepareTransactionalWorkStart(args, defaults);
|
|
751
|
+
if (!prepared.ok)
|
|
752
|
+
return prepared.result;
|
|
753
|
+
try {
|
|
754
|
+
const run = await operations.run(prepared.value.request, prepared.value.options);
|
|
755
|
+
const handle = await freshestOperationHandle(operations, run.submit.handle);
|
|
756
|
+
const task = await transactionalWorkTask(operations, handle);
|
|
757
|
+
return transactionalWorkStartResult(prepared.value, run, handle, task);
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
return transactionalWorkError(args.operationId, error);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function prepareTransactionalWorkStart(args, defaults) {
|
|
764
|
+
const operationId = args.operationId;
|
|
765
|
+
if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
|
|
766
|
+
return {
|
|
767
|
+
ok: false,
|
|
768
|
+
result: transactionalWorkStartUnsupported(operationId, "operationId must be a canonical UUID.", "operationId")
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
if (args.prompt.trim().length === 0) {
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
result: transactionalWorkStartUnsupported(operationId, "prompt must be non-empty.", "prompt")
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
if (args.timeoutMs !== undefined && (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs < 1 || args.timeoutMs > 86_400_000)) {
|
|
778
|
+
return {
|
|
779
|
+
ok: false,
|
|
780
|
+
result: transactionalWorkStartUnsupported(operationId, "timeoutMs must be between 1 and 86400000.", "timeoutMs")
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
const wait = args.wait ?? false;
|
|
784
|
+
const read = args.read ?? false;
|
|
785
|
+
const waitOptions = transactionalWorkWaitOptions(wait, read);
|
|
786
|
+
if (!waitOptions.ok) {
|
|
787
|
+
return { ok: false, result: transactionalWorkStartUnsupported(operationId, waitOptions.message, waitOptions.fieldPath) };
|
|
788
|
+
}
|
|
789
|
+
const configuration = transactionalWorkConfiguration(args.configuration ?? defaults?.configuration);
|
|
790
|
+
if (!configuration.ok) {
|
|
791
|
+
return { ok: false, result: transactionalWorkStartUnsupported(operationId, configuration.message, configuration.fieldPath) };
|
|
792
|
+
}
|
|
793
|
+
const files = (args.files ?? []).map(path => ({ path }));
|
|
794
|
+
const request = {
|
|
795
|
+
schemaVersion: OPERATION_REQUEST_SCHEMA_VERSION,
|
|
796
|
+
operationId,
|
|
797
|
+
surface: "work",
|
|
798
|
+
prompt: args.prompt,
|
|
799
|
+
target: args.newTask === false ? { type: "selected_tab" } : { type: "new" },
|
|
800
|
+
...(configuration.value === undefined ? {} : { configuration: configuration.value }),
|
|
801
|
+
...(files.length === 0 ? {} : { files }),
|
|
802
|
+
capture: {
|
|
803
|
+
responseContent: waitOptions.responseContent,
|
|
804
|
+
responseFormat: waitOptions.responseFormat,
|
|
805
|
+
artifacts: "receipt_only"
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
return {
|
|
809
|
+
ok: true,
|
|
810
|
+
value: {
|
|
811
|
+
request,
|
|
812
|
+
options: {
|
|
813
|
+
...waitOptions.options,
|
|
814
|
+
...(args.timeoutMs === undefined || typeof wait !== "object" || wait.timeoutMs !== undefined
|
|
815
|
+
? {}
|
|
816
|
+
: { timeoutMs: args.timeoutMs })
|
|
817
|
+
},
|
|
818
|
+
readRequested: waitOptions.readRequested,
|
|
819
|
+
responseFormat: waitOptions.responseFormat,
|
|
820
|
+
...(waitOptions.maxResponseChars === undefined ? {} : { maxResponseChars: waitOptions.maxResponseChars })
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function transactionalWorkConfiguration(selected) {
|
|
825
|
+
if (selected === undefined)
|
|
826
|
+
return { ok: true };
|
|
827
|
+
const value = { experience: "work" };
|
|
828
|
+
if (selected.model !== undefined)
|
|
829
|
+
value.model = selected.model;
|
|
830
|
+
const modelVersion = selected.modelVersion ?? selected.version;
|
|
831
|
+
if (modelVersion !== undefined)
|
|
832
|
+
value.modelVersion = modelVersion;
|
|
833
|
+
const additional = {};
|
|
834
|
+
if (selected.effort !== undefined)
|
|
835
|
+
additional.effort = selected.effort;
|
|
836
|
+
if (selected.speed !== undefined)
|
|
837
|
+
additional.speed = selected.speed;
|
|
838
|
+
if (selected.intelligence !== undefined) {
|
|
839
|
+
return {
|
|
840
|
+
ok: false,
|
|
841
|
+
message: "configuration.intelligence is not supported by the transactional Work path.",
|
|
842
|
+
fieldPath: "configuration.intelligence"
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
if (Object.keys(additional).length > 0)
|
|
846
|
+
value.additional = additional;
|
|
847
|
+
return { ok: true, value };
|
|
848
|
+
}
|
|
849
|
+
function transactionalWorkWaitOptions(wait, read) {
|
|
850
|
+
const readRequested = read === true || typeof read === "object";
|
|
851
|
+
const responseFormat = typeof read === "object" && read.format === "text"
|
|
852
|
+
? "text"
|
|
853
|
+
: "markdown";
|
|
854
|
+
const maxResponseChars = typeof read === "object" ? read.maxChars : undefined;
|
|
855
|
+
if (maxResponseChars !== undefined
|
|
856
|
+
&& (!Number.isSafeInteger(maxResponseChars) || maxResponseChars < 0 || maxResponseChars > 8 * 1024 * 1024)) {
|
|
857
|
+
return { ok: false, message: "read.maxChars must be between 0 and 8388608.", fieldPath: "read.maxChars" };
|
|
858
|
+
}
|
|
859
|
+
if (typeof read === "object") {
|
|
860
|
+
if (read.role !== undefined && read.role !== "assistant") {
|
|
861
|
+
return { ok: false, message: "read.role=user is not supported by the transactional Work path.", fieldPath: "read.role" };
|
|
862
|
+
}
|
|
863
|
+
if (read.format !== undefined && read.format !== "markdown" && read.format !== "text") {
|
|
864
|
+
return { ok: false, message: "read.format must be markdown or text on the transactional Work path.", fieldPath: "read.format" };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (typeof wait === "object") {
|
|
868
|
+
if (wait.timeoutMs !== undefined && (!Number.isSafeInteger(wait.timeoutMs) || wait.timeoutMs < 1 || wait.timeoutMs > 86_400_000)) {
|
|
869
|
+
return { ok: false, message: "wait.timeoutMs must be between 1 and 86400000.", fieldPath: "wait.timeoutMs" };
|
|
870
|
+
}
|
|
871
|
+
if (wait.pollMs !== undefined && (!Number.isSafeInteger(wait.pollMs) || wait.pollMs < 0 || wait.pollMs > 60_000)) {
|
|
872
|
+
return { ok: false, message: "wait.pollMs must be between 0 and 60000.", fieldPath: "wait.pollMs" };
|
|
873
|
+
}
|
|
874
|
+
if (wait.responseContent !== undefined && wait.responseContent !== "include" && wait.responseContent !== "metadata") {
|
|
875
|
+
return { ok: false, message: "wait.responseContent must be include or metadata.", fieldPath: "wait.responseContent" };
|
|
876
|
+
}
|
|
877
|
+
for (const [key, value] of [
|
|
878
|
+
["afterTurnCount", wait.afterTurnCount],
|
|
879
|
+
["afterAssistantTurnCount", wait.afterAssistantTurnCount],
|
|
880
|
+
["afterStep", wait.afterStep],
|
|
881
|
+
["stableMs", wait.stableMs],
|
|
882
|
+
["mode", wait.mode]
|
|
883
|
+
]) {
|
|
884
|
+
if (value !== undefined) {
|
|
885
|
+
return { ok: false, message: `wait.${key} is not supported by the transactional Work path.`, fieldPath: `wait.${key}` };
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// A Work read is collect-only. If the caller asks to read without an
|
|
890
|
+
// explicit wait, collect still waits for the exact owned turn rather than
|
|
891
|
+
// falling back to a page-wide latest response.
|
|
892
|
+
const waitForOwnedTurn = wait !== false || readRequested;
|
|
893
|
+
const responseContent = typeof wait === "object" && wait.responseContent !== undefined
|
|
894
|
+
? wait.responseContent
|
|
895
|
+
: readRequested ? "include" : "metadata";
|
|
896
|
+
const options = {
|
|
897
|
+
wait: waitForOwnedTurn,
|
|
898
|
+
responseContent,
|
|
899
|
+
...(typeof wait === "object" && wait.timeoutMs === undefined ? {} : typeof wait === "object" ? { timeoutMs: wait.timeoutMs } : {}),
|
|
900
|
+
...(typeof wait === "object" && wait.pollMs === undefined ? {} : typeof wait === "object" ? { pollIntervalMs: wait.pollMs } : {})
|
|
901
|
+
};
|
|
902
|
+
return {
|
|
903
|
+
ok: true,
|
|
904
|
+
options,
|
|
905
|
+
responseContent,
|
|
906
|
+
readRequested,
|
|
907
|
+
responseFormat,
|
|
908
|
+
...(maxResponseChars === undefined ? {} : { maxResponseChars })
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
async function transactionalWorkTask(operations, handle) {
|
|
912
|
+
try {
|
|
913
|
+
const inspected = await operations.inspect(handle);
|
|
914
|
+
const target = inspected.state.target;
|
|
915
|
+
return {
|
|
916
|
+
...(target?.canonicalThreadUrl === undefined ? {} : { url: target.canonicalThreadUrl }),
|
|
917
|
+
...(target?.conversationId === undefined ? {} : { conversationId: target.conversationId })
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
catch {
|
|
921
|
+
return {};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function transactionalWorkStartResult(prepared, run, handle, task) {
|
|
925
|
+
const base = {
|
|
926
|
+
task,
|
|
927
|
+
responseFormat: prepared.responseFormat,
|
|
928
|
+
operationId: handle.operationId,
|
|
929
|
+
handle,
|
|
930
|
+
requestDigest: handle.requestDigest,
|
|
931
|
+
submitted: {
|
|
932
|
+
submitted: false,
|
|
933
|
+
submissionState: transactionalSubmissionState(handle, "blocker" in run.submit.submission ? run.submit.submission.blocker.mutationBoundary : handle.mutationBoundary),
|
|
934
|
+
completionState: transactionalCompletionState(handle.phase),
|
|
935
|
+
generationActive: handle.phase === "generating"
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
const submission = run.submit.submission;
|
|
939
|
+
if (submission.kind === "blocked" || submission.kind === "uncertain" || submission.kind === "cancelled") {
|
|
940
|
+
const submitted = {
|
|
941
|
+
...base,
|
|
942
|
+
submitted: {
|
|
943
|
+
...base.submitted,
|
|
944
|
+
submitted: base.submitted.submissionState !== "not_submitted"
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
return transactionalWorkBlockerResult(submitted, submission.blocker.code, submission.kind === "uncertain" || submission.blocker.mutationBoundary !== "none", submission.blocker.observationRequired);
|
|
948
|
+
}
|
|
949
|
+
base.submitted = {
|
|
950
|
+
...base.submitted,
|
|
951
|
+
submitted: true,
|
|
952
|
+
submissionState: "submitted",
|
|
953
|
+
completionState: handle.phase === "generating" ? "generating" : handle.phase === "completed" ? "complete" : "unknown",
|
|
954
|
+
generationActive: handle.phase === "generating"
|
|
955
|
+
};
|
|
956
|
+
const collected = run.collect;
|
|
957
|
+
if (collected === undefined) {
|
|
958
|
+
return {
|
|
959
|
+
ok: true,
|
|
960
|
+
status: "ok",
|
|
961
|
+
data: { ...base, pending: true, complete: handle.phase === "completed" },
|
|
962
|
+
warnings: ["Work was submitted through the transactional path; collect the returned handle to observe its exact assistant turn."],
|
|
963
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
if (collected.kind === "completed") {
|
|
967
|
+
const rawText = prepared.request.capture?.responseContent === "include" ? collected.response.rawText : undefined;
|
|
968
|
+
const responseText = rawText === undefined
|
|
969
|
+
? undefined
|
|
970
|
+
: prepared.maxResponseChars === undefined
|
|
971
|
+
? rawText
|
|
972
|
+
: rawText.slice(0, prepared.maxResponseChars);
|
|
973
|
+
const response = prepared.readRequested && responseText !== undefined
|
|
974
|
+
? {
|
|
975
|
+
role: "assistant",
|
|
976
|
+
text: responseText,
|
|
977
|
+
format: prepared.responseFormat === "text" ? "normalized_text" : "markdown",
|
|
978
|
+
completionState: "complete",
|
|
979
|
+
generationActive: false
|
|
980
|
+
}
|
|
981
|
+
: undefined;
|
|
982
|
+
const data = {
|
|
983
|
+
...base,
|
|
984
|
+
complete: true,
|
|
985
|
+
...(collected.response.text?.digest === undefined ? {} : { responseDigest: collected.response.text.digest }),
|
|
986
|
+
...(collected.response.text?.bytes === undefined ? {} : { responseBytes: collected.response.text.bytes }),
|
|
987
|
+
...(response === undefined ? {} : { response }),
|
|
988
|
+
submitted: {
|
|
989
|
+
...base.submitted,
|
|
990
|
+
completionState: "complete",
|
|
991
|
+
generationActive: false
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
return { ok: true, status: "ok", data, warnings: [], context: { timestamp: new Date().toISOString(), experience: "work" } };
|
|
995
|
+
}
|
|
996
|
+
if (collected.kind === "pending") {
|
|
997
|
+
return {
|
|
998
|
+
ok: false,
|
|
999
|
+
status: "partial",
|
|
1000
|
+
data: {
|
|
1001
|
+
...base,
|
|
1002
|
+
pending: true,
|
|
1003
|
+
complete: false,
|
|
1004
|
+
submitted: {
|
|
1005
|
+
...base.submitted,
|
|
1006
|
+
completionState: collected.phase === "generating" ? "generating" : "unknown",
|
|
1007
|
+
generationActive: collected.phase === "generating"
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
warnings: ["Work was submitted exactly once, but completion was not verified."],
|
|
1011
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
return transactionalWorkBlockerResult(base, collected.blocker.code, collected.blocker.mutationBoundary !== "none", true);
|
|
1015
|
+
}
|
|
1016
|
+
function hasTransactionalWorkControl(args) {
|
|
1017
|
+
return args.operationId !== undefined
|
|
1018
|
+
|| args.handle !== undefined
|
|
1019
|
+
|| args.controlActionId !== undefined
|
|
1020
|
+
|| args.expectedAssistantTurnId !== undefined;
|
|
1021
|
+
}
|
|
1022
|
+
async function runTransactionalWorkSteer(args, operations) {
|
|
1023
|
+
const operationId = args.operationId ?? args.handle?.operationId;
|
|
1024
|
+
if (args.prompt.trim().length === 0) {
|
|
1025
|
+
return transactionalWorkSteerUnsupported(operationId, "prompt must be non-empty.", "prompt");
|
|
1026
|
+
}
|
|
1027
|
+
if (args.handle === undefined) {
|
|
1028
|
+
return transactionalWorkSteerUnsupported(operationId, "a durable parent handle is required for transactional Work steer.", "handle");
|
|
1029
|
+
}
|
|
1030
|
+
if (args.handle.surface !== "work") {
|
|
1031
|
+
return transactionalWorkSteerUnsupported(operationId, "the parent handle must belong to the Work surface.", "handle.surface", args.handle);
|
|
1032
|
+
}
|
|
1033
|
+
if (args.handle.phase !== "generating") {
|
|
1034
|
+
return transactionalWorkSteerUnsupported(operationId, "the parent handle must identify a generating Work operation.", "handle.phase", args.handle);
|
|
1035
|
+
}
|
|
1036
|
+
if (args.handle.targetBindingDigest === undefined) {
|
|
1037
|
+
return transactionalWorkSteerUnsupported(operationId, "the parent handle must carry an exact target binding.", "handle.targetBindingDigest", args.handle);
|
|
1038
|
+
}
|
|
1039
|
+
if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
|
|
1040
|
+
return transactionalWorkSteerUnsupported(operationId, "operationId must be a canonical UUID.", "operationId", args.handle);
|
|
1041
|
+
}
|
|
1042
|
+
if (args.handle.operationId !== operationId) {
|
|
1043
|
+
return transactionalWorkSteerUnsupported(operationId, "operationId must match handle.operationId.", "operationId", args.handle);
|
|
1044
|
+
}
|
|
1045
|
+
if (args.controlActionId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(args.controlActionId)) {
|
|
1046
|
+
return transactionalWorkSteerUnsupported(operationId, "controlActionId must be a canonical UUID.", "controlActionId", args.handle);
|
|
1047
|
+
}
|
|
1048
|
+
if (args.expectedAssistantTurnId === undefined || !/^[A-Za-z0-9._:-]{1,512}$/u.test(args.expectedAssistantTurnId)) {
|
|
1049
|
+
return transactionalWorkSteerUnsupported(operationId, "expectedAssistantTurnId must identify one exact assistant turn.", "expectedAssistantTurnId", args.handle);
|
|
1050
|
+
}
|
|
1051
|
+
if (args.wait !== undefined && args.wait !== false) {
|
|
1052
|
+
return transactionalWorkSteerUnsupported(operationId, "wait is not supported by transactional Work steer; collect the parent operation separately.", "wait", args.handle);
|
|
1053
|
+
}
|
|
1054
|
+
if (args.read !== undefined && args.read !== false) {
|
|
1055
|
+
return transactionalWorkSteerUnsupported(operationId, "read is not supported by transactional Work steer; collect the parent operation separately.", "read", args.handle);
|
|
1056
|
+
}
|
|
1057
|
+
if (args.timeoutMs !== undefined && (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs < 0 || args.timeoutMs > 86_400_000)) {
|
|
1058
|
+
return transactionalWorkSteerUnsupported(operationId, "timeoutMs must be between 0 and 86400000.", "timeoutMs", args.handle);
|
|
1059
|
+
}
|
|
1060
|
+
const request = {
|
|
1061
|
+
schemaVersion: OPERATION_CONTROL_REQUEST_SCHEMA_VERSION,
|
|
1062
|
+
controlActionId: args.controlActionId,
|
|
1063
|
+
parent: args.handle,
|
|
1064
|
+
action: "steer",
|
|
1065
|
+
expectedAssistantTurnId: args.expectedAssistantTurnId,
|
|
1066
|
+
steerPrompt: args.prompt,
|
|
1067
|
+
...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
|
|
1068
|
+
};
|
|
1069
|
+
try {
|
|
1070
|
+
const result = await operations.control(request);
|
|
1071
|
+
const data = {
|
|
1072
|
+
operationId,
|
|
1073
|
+
controlActionId: result.controlActionId,
|
|
1074
|
+
requestDigest: result.requestDigest,
|
|
1075
|
+
handle: args.handle,
|
|
1076
|
+
parentHandle: args.handle,
|
|
1077
|
+
...(result.kind === "completed" || result.receipt === undefined ? {} : { control: result.receipt }),
|
|
1078
|
+
...(result.kind === "completed" ? { control: result.receipt } : {})
|
|
1079
|
+
};
|
|
1080
|
+
if (result.kind === "completed") {
|
|
1081
|
+
return {
|
|
1082
|
+
ok: true,
|
|
1083
|
+
status: "ok",
|
|
1084
|
+
data,
|
|
1085
|
+
warnings: ["Work steer was bound to the supplied assistant turn and authorized at most one browser action."],
|
|
1086
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return transactionalWorkBlockerResult(data, result.blocker.code, result.kind === "uncertain" || result.blocker.mutationBoundary !== "none", result.blocker.observationRequired);
|
|
1090
|
+
}
|
|
1091
|
+
catch (error) {
|
|
1092
|
+
return transactionalWorkError(operationId, error, args.controlActionId, args.handle);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function transactionalWorkStartUnsupported(operationId, message, fieldPath) {
|
|
1096
|
+
const unsupported = transactionalUnsupported(operationId, message, fieldPath);
|
|
1097
|
+
if (unsupported.ok) {
|
|
1098
|
+
return {
|
|
1099
|
+
ok: false,
|
|
1100
|
+
status: "error",
|
|
1101
|
+
data: {
|
|
1102
|
+
task: {},
|
|
1103
|
+
submitted: { submitted: false, submissionState: "not_submitted" },
|
|
1104
|
+
...(operationId === undefined ? {} : { operationId })
|
|
1105
|
+
},
|
|
1106
|
+
warnings: [],
|
|
1107
|
+
error: { name: "OperationInputError", message, recoverable: false },
|
|
1108
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
...unsupported.result,
|
|
1113
|
+
data: {
|
|
1114
|
+
task: {},
|
|
1115
|
+
submitted: { submitted: false, submissionState: "not_submitted" },
|
|
1116
|
+
...(operationId === undefined ? {} : { operationId })
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function transactionalWorkSteerUnsupported(operationId, message, fieldPath, handle) {
|
|
1121
|
+
const unsupported = transactionalUnsupported(operationId, message, fieldPath);
|
|
1122
|
+
if (unsupported.ok) {
|
|
1123
|
+
return {
|
|
1124
|
+
ok: false,
|
|
1125
|
+
status: "error",
|
|
1126
|
+
data: {
|
|
1127
|
+
...(operationId === undefined ? {} : { operationId }),
|
|
1128
|
+
...(handle === undefined ? {} : { handle, parentHandle: handle })
|
|
1129
|
+
},
|
|
1130
|
+
warnings: [],
|
|
1131
|
+
error: { name: "OperationInputError", message, recoverable: false },
|
|
1132
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
...unsupported.result,
|
|
1137
|
+
data: {
|
|
1138
|
+
...(operationId === undefined ? {} : { operationId }),
|
|
1139
|
+
...(handle === undefined ? {} : { handle, parentHandle: handle })
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function transactionalWorkBlockerResult(data, code, uncertain, recoverable) {
|
|
1144
|
+
const message = `Transactional Work operation ${uncertain ? "is uncertain" : "was blocked"} (${code.replaceAll("_", " ")}).`;
|
|
1145
|
+
return {
|
|
1146
|
+
ok: false,
|
|
1147
|
+
status: uncertain ? "partial" : "blocked",
|
|
1148
|
+
data,
|
|
1149
|
+
warnings: [],
|
|
1150
|
+
blocker: {
|
|
1151
|
+
kind: transactionalBlockerKind(code),
|
|
1152
|
+
code,
|
|
1153
|
+
message,
|
|
1154
|
+
resumable: recoverable
|
|
1155
|
+
},
|
|
1156
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function transactionalWorkError(operationId, error, controlActionId, parentHandle) {
|
|
1160
|
+
const code = safeOwnErrorCode(error) ?? "operation_error";
|
|
1161
|
+
const message = `Transactional Work operation failed (${code.replaceAll("_", " ")}).`;
|
|
1162
|
+
const blocker = code === "adapter_unavailable"
|
|
1163
|
+
|| code === "browser_bridge_unavailable"
|
|
1164
|
+
|| code === "target_evidence_unavailable"
|
|
1165
|
+
|| code === "backend_unavailable";
|
|
1166
|
+
const data = parentHandle === undefined
|
|
1167
|
+
? {
|
|
1168
|
+
operationId,
|
|
1169
|
+
task: {},
|
|
1170
|
+
submitted: { submitted: false, submissionState: "not_submitted" }
|
|
1171
|
+
}
|
|
1172
|
+
: {
|
|
1173
|
+
operationId,
|
|
1174
|
+
controlActionId,
|
|
1175
|
+
handle: parentHandle,
|
|
1176
|
+
parentHandle
|
|
1177
|
+
};
|
|
1178
|
+
return {
|
|
1179
|
+
ok: false,
|
|
1180
|
+
status: blocker ? "blocked" : "error",
|
|
1181
|
+
data,
|
|
1182
|
+
warnings: [],
|
|
1183
|
+
...(blocker ? { blocker: { kind: transactionalBlockerKind(code), code, message, resumable: true } } : {}),
|
|
1184
|
+
error: { name: "OperationError", message, recoverable: blocker },
|
|
1185
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async function freshestOperationHandle(operations, candidate) {
|
|
1189
|
+
try {
|
|
1190
|
+
const inspected = await operations.inspect(candidate);
|
|
1191
|
+
return inspected.handle;
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
// The submit/collect result is still an authenticated handle and is the
|
|
1195
|
+
// freshest locator available if a browser-free reload is unavailable.
|
|
1196
|
+
return candidate;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function prepareTransactionalAsk(args, defaults, clientOptions) {
|
|
1200
|
+
const operationId = args.operationId;
|
|
1201
|
+
if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
|
|
1202
|
+
return transactionalUnsupported(operationId, "operationId must be a canonical UUID.", "operationId");
|
|
1203
|
+
}
|
|
1204
|
+
if (args.prompt.trim().length === 0) {
|
|
1205
|
+
return transactionalUnsupported(operationId, "prompt must be non-empty.", "prompt");
|
|
1206
|
+
}
|
|
1207
|
+
if (args.download !== undefined) {
|
|
1208
|
+
return transactionalUnsupported(operationId, "download is not supported by the transactional ask path; use operations.collect and an explicit artifact transfer.", "download");
|
|
1209
|
+
}
|
|
1210
|
+
if (args.report !== undefined && args.report !== false) {
|
|
1211
|
+
return transactionalUnsupported(operationId, "report is not supported by the transactional ask path.", "report");
|
|
1212
|
+
}
|
|
1213
|
+
if (clientOptions.reporting?.enabled === true) {
|
|
1214
|
+
return transactionalUnsupported(operationId, "client reporting must be disabled for the transactional ask path.", "reporting");
|
|
1215
|
+
}
|
|
1216
|
+
const target = transactionalTarget(args, defaults);
|
|
1217
|
+
if (!target.ok)
|
|
1218
|
+
return transactionalUnsupported(operationId, target.message, target.fieldPath);
|
|
1219
|
+
const configuration = transactionalConfiguration(args, defaults);
|
|
1220
|
+
if (!configuration.ok)
|
|
1221
|
+
return transactionalUnsupported(operationId, configuration.message, configuration.fieldPath);
|
|
1222
|
+
const wait = args.wait ?? defaults?.wait ?? true;
|
|
1223
|
+
const read = args.read ?? defaults?.read ?? { format: "markdown" };
|
|
1224
|
+
const waitOptions = transactionalWaitOptions(wait, read);
|
|
1225
|
+
if (!waitOptions.ok)
|
|
1226
|
+
return transactionalUnsupported(operationId, waitOptions.message, waitOptions.fieldPath);
|
|
1227
|
+
const files = [
|
|
1228
|
+
...(args.files ?? []),
|
|
1229
|
+
...(args.attachments ?? [])
|
|
1230
|
+
].map(file => typeof file === "string" ? { path: file } : { path: file.path });
|
|
1231
|
+
const request = {
|
|
1232
|
+
schemaVersion: OPERATION_REQUEST_SCHEMA_VERSION,
|
|
1233
|
+
operationId,
|
|
1234
|
+
surface: "chat",
|
|
1235
|
+
prompt: args.prompt,
|
|
1236
|
+
target: target.target,
|
|
1237
|
+
...(configuration.value === undefined ? {} : { configuration: configuration.value }),
|
|
1238
|
+
...(files.length === 0 ? {} : { files }),
|
|
1239
|
+
capture: {
|
|
1240
|
+
responseContent: waitOptions.responseContent,
|
|
1241
|
+
responseFormat: waitOptions.responseFormat,
|
|
1242
|
+
artifacts: "receipt_only"
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
return {
|
|
1246
|
+
ok: true,
|
|
1247
|
+
value: {
|
|
1248
|
+
request,
|
|
1249
|
+
options: waitOptions.options,
|
|
1250
|
+
responseFormat: waitOptions.responseFormat,
|
|
1251
|
+
...(waitOptions.maxResponseChars === undefined ? {} : { maxResponseChars: waitOptions.maxResponseChars })
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
function transactionalTarget(args, defaults) {
|
|
1256
|
+
const thread = args.thread ?? defaults?.thread;
|
|
1257
|
+
const existingTab = args.existingTab ?? defaults?.existingTab;
|
|
1258
|
+
const preferExistingTab = args.preferExistingTab ?? defaults?.preferExistingTab;
|
|
1259
|
+
if (thread !== undefined) {
|
|
1260
|
+
if (existingTab !== undefined || preferExistingTab === true) {
|
|
1261
|
+
return {
|
|
1262
|
+
ok: false,
|
|
1263
|
+
message: "thread cannot be combined with existingTab or preferExistingTab on the transactional ask path.",
|
|
1264
|
+
fieldPath: "thread"
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
return targetFromWorkflowThread(thread);
|
|
1268
|
+
}
|
|
1269
|
+
if (preferExistingTab === true) {
|
|
1270
|
+
if (existingTab !== undefined) {
|
|
1271
|
+
return {
|
|
1272
|
+
ok: false,
|
|
1273
|
+
message: "preferExistingTab cannot be combined with existingTab on the transactional ask path.",
|
|
1274
|
+
fieldPath: "preferExistingTab"
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
return { ok: true, target: { type: "selected_tab" } };
|
|
1278
|
+
}
|
|
1279
|
+
if (existingTab !== undefined)
|
|
1280
|
+
return targetFromExistingTab(existingTab);
|
|
1281
|
+
return { ok: true, target: { type: "new" } };
|
|
1282
|
+
}
|
|
1283
|
+
function targetFromWorkflowThread(thread) {
|
|
1284
|
+
if (isTypedThread(thread)) {
|
|
1285
|
+
switch (thread.type) {
|
|
1286
|
+
case "new":
|
|
1287
|
+
return { ok: true, target: { type: "new" } };
|
|
1288
|
+
case "current":
|
|
1289
|
+
return { ok: true, target: { type: "selected_tab" } };
|
|
1290
|
+
case "url":
|
|
1291
|
+
return thread.url.length > 0
|
|
1292
|
+
? { ok: true, target: { type: "url", url: thread.url } }
|
|
1293
|
+
: { ok: false, message: "thread.url must be non-empty.", fieldPath: "thread.url" };
|
|
1294
|
+
case "conversationId":
|
|
1295
|
+
case "conversation_id":
|
|
1296
|
+
return thread.conversationId.length > 0
|
|
1297
|
+
? { ok: true, target: { type: "conversation_id", conversationId: thread.conversationId } }
|
|
1298
|
+
: { ok: false, message: "thread.conversationId must be non-empty.", fieldPath: "thread.conversationId" };
|
|
1299
|
+
case "search":
|
|
1300
|
+
case "title":
|
|
1301
|
+
return {
|
|
1302
|
+
ok: false,
|
|
1303
|
+
message: "thread search/title selection is not supported by the transactional ask path; supply a URL or conversationId.",
|
|
1304
|
+
fieldPath: "thread"
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (thread.url !== undefined) {
|
|
1309
|
+
return thread.url.length > 0
|
|
1310
|
+
? { ok: true, target: { type: "url", url: thread.url } }
|
|
1311
|
+
: { ok: false, message: "thread.url must be non-empty.", fieldPath: "thread.url" };
|
|
1312
|
+
}
|
|
1313
|
+
if (thread.conversationId !== undefined) {
|
|
1314
|
+
return thread.conversationId.length > 0
|
|
1315
|
+
? { ok: true, target: { type: "conversation_id", conversationId: thread.conversationId } }
|
|
1316
|
+
: { ok: false, message: "thread.conversationId must be non-empty.", fieldPath: "thread.conversationId" };
|
|
1317
|
+
}
|
|
1318
|
+
if (thread.query !== undefined || thread.title !== undefined) {
|
|
1319
|
+
return {
|
|
1320
|
+
ok: false,
|
|
1321
|
+
message: "thread search/title selection is not supported by the transactional ask path; supply a URL or conversationId.",
|
|
1322
|
+
fieldPath: "thread"
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
return { ok: true, target: { type: "new" } };
|
|
1326
|
+
}
|
|
1327
|
+
function targetFromExistingTab(existingTab) {
|
|
1328
|
+
if (existingTab === true)
|
|
1329
|
+
return { ok: true, target: { type: "selected_tab" } };
|
|
1330
|
+
if (existingTab === false)
|
|
1331
|
+
return { ok: true, target: { type: "new" } };
|
|
1332
|
+
if (existingTab.ifMissing !== undefined && existingTab.ifMissing !== "block") {
|
|
1333
|
+
return { ok: false, message: "existingTab.ifMissing must be block on the transactional ask path.", fieldPath: "existingTab.ifMissing" };
|
|
1334
|
+
}
|
|
1335
|
+
if (existingTab.ifMultiple !== undefined && existingTab.ifMultiple !== "block") {
|
|
1336
|
+
return { ok: false, message: "existingTab.ifMultiple must be block on the transactional ask path.", fieldPath: "existingTab.ifMultiple" };
|
|
1337
|
+
}
|
|
1338
|
+
if (existingTab.requireChatGPT === false) {
|
|
1339
|
+
return { ok: false, message: "existingTab.requireChatGPT=false is not supported by the transactional ask path.", fieldPath: "existingTab.requireChatGPT" };
|
|
1340
|
+
}
|
|
1341
|
+
const target = existingTab.target;
|
|
1342
|
+
if (target === undefined || target.type === "selected")
|
|
1343
|
+
return { ok: true, target: { type: "selected_tab" } };
|
|
1344
|
+
switch (target.type) {
|
|
1345
|
+
case "tabId":
|
|
1346
|
+
return target.tabId.length > 0
|
|
1347
|
+
? { ok: true, target: { type: "tab_id", tabId: target.tabId } }
|
|
1348
|
+
: { ok: false, message: "existingTab.target.tabId must be non-empty.", fieldPath: "existingTab.target.tabId" };
|
|
1349
|
+
case "conversationId":
|
|
1350
|
+
case "conversation_id":
|
|
1351
|
+
return target.conversationId.length > 0
|
|
1352
|
+
? { ok: true, target: { type: "conversation_id", conversationId: target.conversationId } }
|
|
1353
|
+
: { ok: false, message: "existingTab.target.conversationId must be non-empty.", fieldPath: "existingTab.target.conversationId" };
|
|
1354
|
+
case "url":
|
|
1355
|
+
return target.url.length > 0
|
|
1356
|
+
? { ok: true, target: { type: "url", url: target.url } }
|
|
1357
|
+
: { ok: false, message: "existingTab.target.url must be non-empty.", fieldPath: "existingTab.target.url" };
|
|
1358
|
+
case "title":
|
|
1359
|
+
return { ok: false, message: "existingTab title selection is not supported by the transactional ask path; supply a tabId, URL, or conversationId.", fieldPath: "existingTab.target" };
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function transactionalConfiguration(args, defaults) {
|
|
1363
|
+
const experience = args.experience ?? defaults?.experience;
|
|
1364
|
+
if (experience === "work") {
|
|
1365
|
+
return { ok: false, message: "experience=work is not supported by the transactional chat ask path.", fieldPath: "experience" };
|
|
1366
|
+
}
|
|
1367
|
+
const selected = args.configuration ?? defaults?.configuration;
|
|
1368
|
+
const mode = args.mode ?? defaults?.mode;
|
|
1369
|
+
if (mode?.timeoutMs !== undefined) {
|
|
1370
|
+
return { ok: false, message: "mode.timeoutMs is not supported by the transactional ask path.", fieldPath: "mode.timeoutMs" };
|
|
1371
|
+
}
|
|
1372
|
+
const values = {};
|
|
1373
|
+
if (experience !== undefined)
|
|
1374
|
+
values.experience = experience;
|
|
1375
|
+
const model = mergeConfigurationValue("model", selected?.model, mode?.model);
|
|
1376
|
+
if (!model.ok)
|
|
1377
|
+
return model;
|
|
1378
|
+
if (model.value !== undefined)
|
|
1379
|
+
values.model = model.value;
|
|
1380
|
+
const modelVersion = mergeConfigurationValue("modelVersion", selected?.modelVersion ?? selected?.version, mode?.modelVersion ?? mode?.version);
|
|
1381
|
+
if (!modelVersion.ok)
|
|
1382
|
+
return modelVersion;
|
|
1383
|
+
if (modelVersion.value !== undefined)
|
|
1384
|
+
values.modelVersion = modelVersion.value;
|
|
1385
|
+
const additional = {};
|
|
1386
|
+
for (const [axis, first, second] of [
|
|
1387
|
+
["intelligence", selected?.intelligence, mode?.intelligence],
|
|
1388
|
+
["effort", selected?.effort, mode?.effort],
|
|
1389
|
+
["speed", selected?.speed, undefined]
|
|
1390
|
+
]) {
|
|
1391
|
+
const merged = mergeConfigurationValue(axis, first, second);
|
|
1392
|
+
if (!merged.ok)
|
|
1393
|
+
return merged;
|
|
1394
|
+
if (merged.value !== undefined)
|
|
1395
|
+
additional[axis] = merged.value;
|
|
1396
|
+
}
|
|
1397
|
+
const tools = args.tools;
|
|
1398
|
+
if (tools !== undefined) {
|
|
1399
|
+
for (const [index, tool] of tools.entries()) {
|
|
1400
|
+
if (tool.tool.trim().length === 0) {
|
|
1401
|
+
return { ok: false, message: "tool must be non-empty.", fieldPath: `tools[${index}].tool` };
|
|
1402
|
+
}
|
|
1403
|
+
if (tool.timeoutMs !== undefined) {
|
|
1404
|
+
return { ok: false, message: "tool timeoutMs is not supported by the transactional ask path.", fieldPath: `tools[${index}].timeoutMs` };
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (tools.length > 0)
|
|
1408
|
+
values.tools = tools.map(tool => tool.tool);
|
|
1409
|
+
}
|
|
1410
|
+
if (Object.keys(additional).length > 0)
|
|
1411
|
+
values.additional = additional;
|
|
1412
|
+
return Object.keys(values).length === 0 ? { ok: true } : { ok: true, value: values };
|
|
1413
|
+
}
|
|
1414
|
+
function mergeConfigurationValue(axis, first, second) {
|
|
1415
|
+
if (first !== undefined && second !== undefined && first !== second) {
|
|
1416
|
+
return {
|
|
1417
|
+
ok: false,
|
|
1418
|
+
message: `configuration and mode disagree for ${axis}; provide one value.`,
|
|
1419
|
+
fieldPath: axis === "modelVersion" ? "configuration.modelVersion" : `configuration.${axis}`
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
if (first === undefined && second === undefined)
|
|
1423
|
+
return { ok: true };
|
|
1424
|
+
const value = first ?? second;
|
|
1425
|
+
return value === undefined ? { ok: true } : { ok: true, value };
|
|
1426
|
+
}
|
|
1427
|
+
function transactionalWaitOptions(wait, read) {
|
|
1428
|
+
const readRequested = read === true || typeof read === "object";
|
|
1429
|
+
const responseFormat = typeof read === "object" && read.format === "text"
|
|
1430
|
+
? "text"
|
|
1431
|
+
: "markdown";
|
|
1432
|
+
const maxResponseChars = typeof read === "object" ? read.maxChars : undefined;
|
|
1433
|
+
if (maxResponseChars !== undefined && (!Number.isSafeInteger(maxResponseChars) || maxResponseChars < 0 || maxResponseChars > 8 * 1024 * 1024)) {
|
|
1434
|
+
return { ok: false, message: "read.maxChars must be between 0 and 8388608.", fieldPath: "read.maxChars" };
|
|
1435
|
+
}
|
|
1436
|
+
if (typeof read === "object") {
|
|
1437
|
+
if (read.role !== undefined && read.role !== "assistant") {
|
|
1438
|
+
return { ok: false, message: "read.role=user is not supported by the transactional ask path.", fieldPath: "read.role" };
|
|
1439
|
+
}
|
|
1440
|
+
if (read.format !== undefined && read.format !== "markdown" && read.format !== "text") {
|
|
1441
|
+
return { ok: false, message: "read.format must be markdown or text on the transactional ask path.", fieldPath: "read.format" };
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (typeof wait === "object") {
|
|
1445
|
+
if (wait.timeoutMs !== undefined && (!Number.isSafeInteger(wait.timeoutMs) || wait.timeoutMs < 1 || wait.timeoutMs > 86_400_000)) {
|
|
1446
|
+
return { ok: false, message: "wait.timeoutMs must be between 1 and 86400000.", fieldPath: "wait.timeoutMs" };
|
|
1447
|
+
}
|
|
1448
|
+
if (wait.pollMs !== undefined && (!Number.isSafeInteger(wait.pollMs) || wait.pollMs < 0 || wait.pollMs > 60_000)) {
|
|
1449
|
+
return { ok: false, message: "wait.pollMs must be between 0 and 60000.", fieldPath: "wait.pollMs" };
|
|
1450
|
+
}
|
|
1451
|
+
if (wait.responseContent !== undefined && wait.responseContent !== "include" && wait.responseContent !== "metadata") {
|
|
1452
|
+
return { ok: false, message: "wait.responseContent must be include or metadata.", fieldPath: "wait.responseContent" };
|
|
1453
|
+
}
|
|
1454
|
+
for (const [key, value] of [
|
|
1455
|
+
["afterTurnCount", wait.afterTurnCount],
|
|
1456
|
+
["afterAssistantTurnCount", wait.afterAssistantTurnCount],
|
|
1457
|
+
["afterStep", wait.afterStep],
|
|
1458
|
+
["stableMs", wait.stableMs],
|
|
1459
|
+
["mode", wait.mode]
|
|
1460
|
+
]) {
|
|
1461
|
+
if (value !== undefined) {
|
|
1462
|
+
return { ok: false, message: `wait.${key} is not supported by the transactional ask path.`, fieldPath: `wait.${key}` };
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const responseContent = typeof wait === "object" && wait.responseContent !== undefined
|
|
1467
|
+
? wait.responseContent
|
|
1468
|
+
: readRequested ? "include" : "metadata";
|
|
1469
|
+
const options = {
|
|
1470
|
+
wait: wait !== false,
|
|
1471
|
+
responseContent,
|
|
1472
|
+
...(typeof wait === "object" && wait.timeoutMs === undefined ? {} : typeof wait === "object" ? { timeoutMs: wait.timeoutMs } : {}),
|
|
1473
|
+
...(typeof wait === "object" && wait.pollMs === undefined ? {} : typeof wait === "object" ? { pollIntervalMs: wait.pollMs } : {})
|
|
1474
|
+
};
|
|
1475
|
+
return {
|
|
1476
|
+
ok: true,
|
|
1477
|
+
options,
|
|
1478
|
+
responseContent,
|
|
1479
|
+
readRequested,
|
|
1480
|
+
responseFormat,
|
|
1481
|
+
...(maxResponseChars === undefined ? {} : { maxResponseChars })
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function transactionalUnsupported(operationId, message, fieldPath) {
|
|
1485
|
+
const data = operationId === undefined ? {} : { operationId };
|
|
1486
|
+
return {
|
|
1487
|
+
ok: false,
|
|
1488
|
+
result: {
|
|
1489
|
+
ok: false,
|
|
1490
|
+
status: "unsupported",
|
|
1491
|
+
data,
|
|
1492
|
+
warnings: [],
|
|
1493
|
+
error: { name: "OperationInputError", message, recoverable: false },
|
|
1494
|
+
blocker: { kind: "unknown", code: "unsupported_operation_input", fieldPath, message },
|
|
1495
|
+
context: { timestamp: new Date().toISOString() }
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function transactionalAskCommandResult(prepared, run, handle) {
|
|
1500
|
+
const base = {
|
|
1501
|
+
operationId: handle.operationId,
|
|
1502
|
+
responseFormat: prepared.responseFormat,
|
|
1503
|
+
handle,
|
|
1504
|
+
requestDigest: handle.requestDigest
|
|
1505
|
+
};
|
|
1506
|
+
const submission = run.submit.submission;
|
|
1507
|
+
if (submission.kind === "blocked" || submission.kind === "uncertain" || submission.kind === "cancelled") {
|
|
1508
|
+
return transactionalBlockerResult({
|
|
1509
|
+
...base,
|
|
1510
|
+
submissionState: transactionalSubmissionState(handle, submission.blocker.mutationBoundary),
|
|
1511
|
+
complete: false,
|
|
1512
|
+
completionState: transactionalCompletionState(handle.phase),
|
|
1513
|
+
generationActive: handle.phase === "generating"
|
|
1514
|
+
}, submission.blocker.code, submission.blocker.mutationBoundary !== "none", submission.blocker.observationRequired);
|
|
1515
|
+
}
|
|
1516
|
+
if (run.collect === undefined) {
|
|
1517
|
+
if (submission.kind === "completed_receipt") {
|
|
1518
|
+
return {
|
|
1519
|
+
ok: true,
|
|
1520
|
+
status: "ok",
|
|
1521
|
+
data: { ...base, submissionState: "submitted", complete: true, completionState: "complete", generationActive: false },
|
|
1522
|
+
warnings: [],
|
|
1523
|
+
context: { timestamp: new Date().toISOString() }
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
ok: false,
|
|
1528
|
+
status: "partial",
|
|
1529
|
+
data: { ...base, pending: true, complete: false, completionState: "generating", generationActive: true, submissionState: "submitted_generating" },
|
|
1530
|
+
warnings: [],
|
|
1531
|
+
context: { timestamp: new Date().toISOString() }
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const collected = run.collect;
|
|
1535
|
+
if (collected.kind === "completed") {
|
|
1536
|
+
const rawText = prepared.request.capture?.responseContent === "include" ? collected.response.rawText : undefined;
|
|
1537
|
+
const responseText = rawText === undefined
|
|
1538
|
+
? undefined
|
|
1539
|
+
: prepared.maxResponseChars === undefined
|
|
1540
|
+
? rawText
|
|
1541
|
+
: rawText.slice(0, prepared.maxResponseChars);
|
|
1542
|
+
const data = {
|
|
1543
|
+
...base,
|
|
1544
|
+
submissionState: "submitted",
|
|
1545
|
+
complete: true,
|
|
1546
|
+
completionState: "complete",
|
|
1547
|
+
generationActive: false,
|
|
1548
|
+
...(responseText === undefined ? {} : { responseText }),
|
|
1549
|
+
...(collected.response.text?.digest === undefined ? {} : { responseDigest: collected.response.text.digest }),
|
|
1550
|
+
...(collected.response.text?.bytes === undefined ? {} : { responseBytes: collected.response.text.bytes }),
|
|
1551
|
+
artifacts: collected.response.artifacts
|
|
1552
|
+
};
|
|
1553
|
+
return { ok: true, status: "ok", data, warnings: [], context: { timestamp: new Date().toISOString() } };
|
|
1554
|
+
}
|
|
1555
|
+
if (collected.kind === "pending") {
|
|
1556
|
+
return {
|
|
1557
|
+
ok: false,
|
|
1558
|
+
status: "partial",
|
|
1559
|
+
data: { ...base, pending: true, complete: false, completionState: collected.phase === "generating" ? "generating" : "unknown", generationActive: collected.phase === "generating", submissionState: "submitted_generating" },
|
|
1560
|
+
warnings: [],
|
|
1561
|
+
context: { timestamp: new Date().toISOString() }
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
return transactionalBlockerResult({
|
|
1565
|
+
...base,
|
|
1566
|
+
submissionState: transactionalSubmissionState(handle, collected.blocker.mutationBoundary),
|
|
1567
|
+
complete: false,
|
|
1568
|
+
completionState: transactionalCompletionState(handle.phase),
|
|
1569
|
+
generationActive: handle.phase === "generating"
|
|
1570
|
+
}, collected.blocker.code, collected.blocker.mutationBoundary !== "none", true);
|
|
1571
|
+
}
|
|
1572
|
+
function transactionalSubmissionState(handle, boundary) {
|
|
1573
|
+
if (["submitted", "generating", "capturing", "completed"].includes(handle.phase)) {
|
|
1574
|
+
return handle.phase === "generating" ? "submitted_generating" : "submitted";
|
|
1575
|
+
}
|
|
1576
|
+
return boundary === "send_may_have_occurred" || boundary === "control_may_have_occurred"
|
|
1577
|
+
? "submitted_unconfirmed"
|
|
1578
|
+
: "not_submitted";
|
|
1579
|
+
}
|
|
1580
|
+
function transactionalCompletionState(phase) {
|
|
1581
|
+
if (phase === "completed")
|
|
1582
|
+
return "complete";
|
|
1583
|
+
if (phase === "generating")
|
|
1584
|
+
return "generating";
|
|
1585
|
+
if (phase === "uncertain" || phase === "capturing")
|
|
1586
|
+
return "partial";
|
|
1587
|
+
return "unknown";
|
|
1588
|
+
}
|
|
1589
|
+
function transactionalBlockerResult(data, code, uncertain, recoverable) {
|
|
1590
|
+
const kind = transactionalBlockerKind(code);
|
|
1591
|
+
const message = `Transactional operation ${uncertain ? "is uncertain" : "was blocked"} (${code.replaceAll("_", " ")}).`;
|
|
1592
|
+
return {
|
|
1593
|
+
ok: false,
|
|
1594
|
+
status: uncertain ? "partial" : "blocked",
|
|
1595
|
+
data,
|
|
1596
|
+
warnings: [],
|
|
1597
|
+
blocker: { kind, code, message, resumable: recoverable },
|
|
1598
|
+
context: { timestamp: new Date().toISOString() }
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function transactionalBlockerKind(code) {
|
|
1602
|
+
if (code === "browser_bridge_unavailable")
|
|
1603
|
+
return "browser_bridge_unavailable";
|
|
1604
|
+
if (code === "login_required")
|
|
1605
|
+
return "login_required";
|
|
1606
|
+
if (code === "captcha")
|
|
1607
|
+
return "captcha";
|
|
1608
|
+
if (code === "rate_limited")
|
|
1609
|
+
return "rate_limit";
|
|
1610
|
+
if (code === "permission_required" || code === "input_file_changed")
|
|
1611
|
+
return "permission";
|
|
1612
|
+
if (code === "needs_confirmation")
|
|
1613
|
+
return "confirmation";
|
|
1614
|
+
if (code.includes("artifact"))
|
|
1615
|
+
return "artifact_unavailable";
|
|
1616
|
+
if (code.includes("selector") || code.includes("configuration") || code.includes("target") || code.includes("turn"))
|
|
1617
|
+
return "selector_drift";
|
|
1618
|
+
if (code.includes("file") || code.includes("attachment"))
|
|
1619
|
+
return "upload_failed";
|
|
1620
|
+
return "unknown";
|
|
1621
|
+
}
|
|
1622
|
+
function transactionalAskError(operationId, error) {
|
|
1623
|
+
const code = safeOwnErrorCode(error) ?? "operation_error";
|
|
1624
|
+
const blocker = code === "adapter_unavailable" || code === "browser_bridge_unavailable" || code === "target_evidence_unavailable";
|
|
1625
|
+
const message = `Transactional operation failed (${code.replaceAll("_", " ")}).`;
|
|
1626
|
+
return {
|
|
1627
|
+
ok: false,
|
|
1628
|
+
status: blocker ? "blocked" : "error",
|
|
1629
|
+
data: { operationId },
|
|
1630
|
+
warnings: [],
|
|
1631
|
+
...(blocker ? { blocker: { kind: transactionalBlockerKind(code), code, message, resumable: true } } : {}),
|
|
1632
|
+
error: { name: "OperationError", message, recoverable: blocker },
|
|
1633
|
+
context: { timestamp: new Date().toISOString() }
|
|
1634
|
+
};
|
|
508
1635
|
}
|
|
509
1636
|
function planAskWorkflow(args, defaults = {}) {
|
|
510
1637
|
const thread = args.thread ?? { type: "new" };
|
|
@@ -786,6 +1913,42 @@ function normalizeFileInputs(files) {
|
|
|
786
1913
|
function isRecord(value) {
|
|
787
1914
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
788
1915
|
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Read only a bounded own data property. Provider/runtime failures are an
|
|
1918
|
+
* untrusted boundary: consulting an inherited property or accessor can run
|
|
1919
|
+
* arbitrary code and can surface private diagnostics while formatting a
|
|
1920
|
+
* public result.
|
|
1921
|
+
*/
|
|
1922
|
+
function safeOwnErrorCode(value) {
|
|
1923
|
+
if (!isRecord(value))
|
|
1924
|
+
return undefined;
|
|
1925
|
+
let descriptor;
|
|
1926
|
+
try {
|
|
1927
|
+
descriptor = Object.getOwnPropertyDescriptor(value, "code");
|
|
1928
|
+
}
|
|
1929
|
+
catch {
|
|
1930
|
+
return undefined;
|
|
1931
|
+
}
|
|
1932
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
1933
|
+
return undefined;
|
|
1934
|
+
return typeof descriptor.value === "string" && /^[a-z][a-z0-9_]{0,63}$/u.test(descriptor.value)
|
|
1935
|
+
? descriptor.value
|
|
1936
|
+
: undefined;
|
|
1937
|
+
}
|
|
1938
|
+
function readOperationIdFromUnknown(value) {
|
|
1939
|
+
if (!isRecord(value))
|
|
1940
|
+
return undefined;
|
|
1941
|
+
try {
|
|
1942
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, "operationId");
|
|
1943
|
+
const operationId = descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
|
|
1944
|
+
return typeof operationId === "string" && TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)
|
|
1945
|
+
? operationId
|
|
1946
|
+
: undefined;
|
|
1947
|
+
}
|
|
1948
|
+
catch {
|
|
1949
|
+
return undefined;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
789
1952
|
function stringInput(input, key) {
|
|
790
1953
|
const value = input[key];
|
|
791
1954
|
if (typeof value !== "string" || value.length === 0) {
|