@ai-workbench/remote-agent 0.0.0-stage → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -2
- package/cordis.patch.yml +3 -0
- package/dist/acp.d.ts +46 -0
- package/dist/acp.js +303 -0
- package/dist/agents.d.ts +15 -0
- package/dist/agents.js +4 -0
- package/dist/broker.d.ts +67 -0
- package/dist/broker.js +397 -0
- package/dist/client.js +36 -0
- package/dist/connect-contract.d.ts +26 -0
- package/dist/connect-contract.js +1 -0
- package/dist/host-layout.d.ts +8 -0
- package/dist/host-layout.js +9 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +293 -0
- package/dist/input.d.ts +12 -0
- package/dist/input.js +49 -0
- package/dist/interaction.d.ts +19 -0
- package/dist/interaction.js +1 -0
- package/dist/job.d.ts +17 -0
- package/dist/job.js +57 -0
- package/dist/mapping.d.ts +40 -0
- package/dist/mapping.js +266 -0
- package/dist/oneshot.d.ts +13 -0
- package/dist/oneshot.js +78 -0
- package/dist/presentation.d.ts +6 -0
- package/dist/presentation.js +41 -0
- package/dist/provider-locale.d.ts +19 -0
- package/dist/provider-locale.js +73 -0
- package/dist/provider.d.ts +135 -0
- package/dist/provider.js +158 -0
- package/dist/readiness.d.ts +28 -0
- package/dist/readiness.js +192 -0
- package/dist/remote.d.ts +34 -0
- package/dist/remote.js +51 -0
- package/dist/sdk-ssh-wrapper.d.ts +2 -0
- package/dist/sdk-ssh-wrapper.js +34 -0
- package/dist/workspace-notice.d.ts +26 -0
- package/dist/workspace-notice.js +254 -0
- package/locale/en.json +10 -0
- package/locale/zh.json +10 -0
- package/package.json +97 -4
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { NeedError } from "./remote.js";
|
|
2
|
+
const usageCounters = ['inputTokens', 'outputTokens', 'cachedReadTokens', 'cachedWriteTokens', 'thoughtTokens', 'totalTokens'];
|
|
3
|
+
const optionalUsageCounters = ['cachedReadTokens', 'cachedWriteTokens', 'thoughtTokens', 'totalTokens'];
|
|
4
|
+
/** Remote tools are reported as text; they must never become local DSH tool invocations. */
|
|
5
|
+
export class StreamMapper {
|
|
6
|
+
index = 0;
|
|
7
|
+
seen = new Map();
|
|
8
|
+
baseline;
|
|
9
|
+
/** Omitted argument is exclusively for a known new session; pass null on unknown resume. */
|
|
10
|
+
constructor(previousUsage = { version: 1, kind: 'zero' }) {
|
|
11
|
+
if (previousUsage !== null && !validUsageSnapshot(previousUsage))
|
|
12
|
+
throw new NeedError('USAGE_BASELINE_INVALID', '已保存的远端累计用量基线无效,不能重复结算历史用量', '与原远端会话关联的有效用量快照');
|
|
13
|
+
this.baseline = previousUsage === null ? null : { ...previousUsage };
|
|
14
|
+
}
|
|
15
|
+
get usageSnapshot() { return this.baseline === null ? null : { ...this.baseline }; }
|
|
16
|
+
text(text, reasoning = false) {
|
|
17
|
+
if (!text)
|
|
18
|
+
return [];
|
|
19
|
+
const index = this.index++;
|
|
20
|
+
return [{ type: 'block-start', index, blockType: reasoning ? 'reasoning' : 'text' },
|
|
21
|
+
reasoning ? { type: 'reasoning-delta', index, text } : { type: 'text-delta', index, text },
|
|
22
|
+
{ type: 'block-end', index, block: { type: reasoning ? 'reasoning' : 'text', text } }];
|
|
23
|
+
}
|
|
24
|
+
acp(update) {
|
|
25
|
+
let chunks;
|
|
26
|
+
switch (update.sessionUpdate) {
|
|
27
|
+
case 'agent_message_chunk':
|
|
28
|
+
case 'agent_thought_chunk':
|
|
29
|
+
chunks = this.content(update.content, update.sessionUpdate === 'agent_thought_chunk');
|
|
30
|
+
break;
|
|
31
|
+
case 'user_message_chunk':
|
|
32
|
+
chunks = this.text('\n[远端输入回显]\n').concat(this.content(update.content));
|
|
33
|
+
break;
|
|
34
|
+
case 'tool_call':
|
|
35
|
+
case 'tool_call_update':
|
|
36
|
+
chunks = this.text(`\n[远端工具] ${update.title ?? update.toolCallId} · ${toolStatus(update.status)}\n`);
|
|
37
|
+
for (const content of update.content ?? [])
|
|
38
|
+
chunks.push(...this.toolContent(content));
|
|
39
|
+
break;
|
|
40
|
+
case 'usage_update':
|
|
41
|
+
chunks = this.text(`\n[远端上下文] ${update.used}/${update.size} 令牌${update.cost ? ` · 累计费用 ${update.cost.amount} ${update.cost.currency}` : ''};生成用量尚未提供,等待远端本轮真实计数。\n`);
|
|
42
|
+
break;
|
|
43
|
+
case 'plan':
|
|
44
|
+
chunks = this.text('\n[远端计划]\n' + update.entries.map(e => `${planStatus(e.status)}: ${e.content}`).join('\n') + '\n');
|
|
45
|
+
break;
|
|
46
|
+
case 'plan_update':
|
|
47
|
+
chunks = this.text('\n[远端计划更新]\n' + (update.plan.type === 'markdown' ? update.plan.content : update.plan.type === 'file' ? `远端计划 URI(仅供核查):${plainUri(update.plan.uri)}` : update.plan.entries.map(e => `${planStatus(e.status)}: ${e.content}`).join('\n')) + '\n');
|
|
48
|
+
break;
|
|
49
|
+
case 'plan_removed':
|
|
50
|
+
chunks = this.text(`\n[远端计划] 已移除计划 ${update.planId}\n`);
|
|
51
|
+
break;
|
|
52
|
+
case 'notice':
|
|
53
|
+
chunks = this.text(`\n[远端通知] ${update.title}${update.description ? ': ' + update.description : ''}\n`);
|
|
54
|
+
break;
|
|
55
|
+
case 'current_mode_update':
|
|
56
|
+
chunks = this.text(`\n[远端模式] ${update.currentModeId}\n`);
|
|
57
|
+
break;
|
|
58
|
+
case 'available_commands_update':
|
|
59
|
+
chunks = this.text('\n[远端命令] ' + update.availableCommands.map(c => `${c.name}${c.description ? `:${c.description}` : ''}`).join(';') + '\n');
|
|
60
|
+
break;
|
|
61
|
+
case 'config_option_update':
|
|
62
|
+
chunks = this.text('\n[远端配置] ' + update.configOptions.map(c => c.name).join('、') + '已更新;首版通过远端官方配置管理。\n');
|
|
63
|
+
break;
|
|
64
|
+
case 'session_info_update':
|
|
65
|
+
chunks = this.text(`\n[远端会话信息] ${update.title ?? '会话信息已更新'}\n`);
|
|
66
|
+
break;
|
|
67
|
+
case 'compaction_update':
|
|
68
|
+
chunks = this.text(`\n[远端上下文整理] ${update.status}\n`);
|
|
69
|
+
break;
|
|
70
|
+
case 'compaction_summary_chunk':
|
|
71
|
+
chunks = this.text('\n[远端上下文摘要]\n').concat(this.content(update.content));
|
|
72
|
+
break;
|
|
73
|
+
case 'session_message_chunk':
|
|
74
|
+
chunks = this.text('\n[远端会话消息]\n').concat(this.content(update.content));
|
|
75
|
+
break;
|
|
76
|
+
case 'session_message':
|
|
77
|
+
chunks = this.text('\n[远端会话消息]\n');
|
|
78
|
+
for (const content of update.content ?? [])
|
|
79
|
+
chunks.push(...this.content(content));
|
|
80
|
+
break;
|
|
81
|
+
default: chunks = this.text(`\n[远端事件] 首版不支持此事件:${safeLabel(update.sessionUpdate)};需要对应的公开显示映射。\n`);
|
|
82
|
+
}
|
|
83
|
+
return chunks.concat(this.meta(update._meta));
|
|
84
|
+
}
|
|
85
|
+
/** ACP usage and the fixed Claude quota both accumulate across the session, not per turn. */
|
|
86
|
+
acpResponse(response) {
|
|
87
|
+
const quota = record(response._meta?.quota);
|
|
88
|
+
const tokens = record(quota?.token_count);
|
|
89
|
+
const usage = response.usage;
|
|
90
|
+
let chunks;
|
|
91
|
+
if (usage) {
|
|
92
|
+
chunks = this.cumulativeUsage('usage', usage.inputTokens, usage.outputTokens, usage.cachedReadTokens, usage.cachedWriteTokens, usage.thoughtTokens, usage.totalTokens);
|
|
93
|
+
}
|
|
94
|
+
else if (tokens) {
|
|
95
|
+
// Fixed Claude adapter's quotaTokenCount: inputTokens excludes both
|
|
96
|
+
// cachedInputTokens (reads) and cachedWriteTokens (writes).
|
|
97
|
+
chunks = this.cumulativeUsage('quota', tokens.inputTokens, tokens.outputTokens, tokens.cachedInputTokens, tokens.cachedWriteTokens, undefined, tokens.totalTokens);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.usageGap();
|
|
101
|
+
chunks = this.text('\n[远端用量] 本轮生成用量未提供;上下文占用不能替代输入或输出计数。\n');
|
|
102
|
+
}
|
|
103
|
+
return chunks.concat(this.meta(response._meta, ['quota']));
|
|
104
|
+
}
|
|
105
|
+
usageGap() { this.baseline = this.baseline?.kind === 'cumulative' ? { ...this.baseline, gap: true } : null; }
|
|
106
|
+
cumulativeUsage(source, input, output, read, write, thought, total) {
|
|
107
|
+
const values = [input, output, ...(read == null ? [] : [read]), ...(write == null ? [] : [write]), ...(thought == null ? [] : [thought]), ...(total == null ? [] : [total])];
|
|
108
|
+
if (!values.every(validCount)) {
|
|
109
|
+
this.usageGap();
|
|
110
|
+
return this.text('\n[远端用量] 远端累计计数无效,本轮未记录生成用量;需要有效的输入、输出与缓存计数。\n');
|
|
111
|
+
}
|
|
112
|
+
const current = { version: 1, kind: 'cumulative', source, inputTokens: input, outputTokens: output,
|
|
113
|
+
...(read == null ? {} : { cachedReadTokens: read }), ...(write == null ? {} : { cachedWriteTokens: write }),
|
|
114
|
+
...(thought == null ? {} : { thoughtTokens: thought }), ...(total == null ? {} : { totalTokens: total }) };
|
|
115
|
+
const previous = this.baseline;
|
|
116
|
+
if (previous?.kind === 'cumulative' && previous.source === source) {
|
|
117
|
+
if (usageCounters.some(key => current[key] !== undefined && previous[key] !== undefined && current[key] < previous[key])) {
|
|
118
|
+
this.usageGap();
|
|
119
|
+
return this.text('\n[远端用量] 远端累计计数发生回退,本轮未记录生成用量;已保留此前高水位,需要核对远端会话计数。\n');
|
|
120
|
+
}
|
|
121
|
+
if (optionalUsageCounters.some(key => previous[key] !== undefined && current[key] === undefined)) {
|
|
122
|
+
this.baseline = { ...previous, ...current, gap: true };
|
|
123
|
+
return this.text('\n[远端用量] 可选累计计数缺失,本轮未记录生成用量;未把缺失值补为零,等待完整计数重建基线。\n');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const sum = current.inputTokens + current.outputTokens + (current.cachedReadTokens ?? 0) + (current.cachedWriteTokens ?? 0);
|
|
127
|
+
if (!Number.isSafeInteger(sum) || (current.totalTokens !== undefined && current.totalTokens !== sum) || (current.thoughtTokens !== undefined && current.thoughtTokens > current.outputTokens)) {
|
|
128
|
+
this.usageGap();
|
|
129
|
+
return this.text('\n[远端用量] 输入、输出、缓存与合计计数不一致,本轮未记录生成用量;需要远端提供语义一致的真实计数。\n');
|
|
130
|
+
}
|
|
131
|
+
this.baseline = current;
|
|
132
|
+
if (previous === null || previous.kind === 'cumulative' && (previous.source !== source || previous.gap || optionalUsageCounters.some(key => previous[key] === undefined && current[key] !== undefined))) {
|
|
133
|
+
return this.text('\n[远端用量] 已建立当前累计用量基线;本轮未记录生成用量,避免把无法核对的历史累计重复结算。\n');
|
|
134
|
+
}
|
|
135
|
+
if (previous.kind === 'zero')
|
|
136
|
+
return this.usage(current.inputTokens, current.outputTokens, current.cachedReadTokens, current.cachedWriteTokens, current.thoughtTokens, current.totalTokens);
|
|
137
|
+
const delta = (key) => current[key] === undefined ? undefined : current[key] - previous[key];
|
|
138
|
+
if (!usageCounters.some(key => (delta(key) ?? 0) !== 0))
|
|
139
|
+
return [];
|
|
140
|
+
return this.usage(delta('inputTokens'), delta('outputTokens'), delta('cachedReadTokens'), delta('cachedWriteTokens'), delta('thoughtTokens'), delta('totalTokens'));
|
|
141
|
+
}
|
|
142
|
+
usage(input, output, read, write, thought, total) {
|
|
143
|
+
const values = [input, output, ...(read == null ? [] : [read]), ...(write == null ? [] : [write]), ...(thought == null ? [] : [thought]), ...(total == null ? [] : [total])];
|
|
144
|
+
if (!values.every(n => typeof n === 'number' && Number.isSafeInteger(n) && n >= 0)) {
|
|
145
|
+
return this.text('\n[远端用量] 远端真实计数无效,本轮未记录生成用量;需要有效的输入、输出与缓存计数。\n');
|
|
146
|
+
}
|
|
147
|
+
const sum = input + output + (read ?? 0) + (write ?? 0);
|
|
148
|
+
if ((total != null && total !== sum) || (thought != null && thought > output)) {
|
|
149
|
+
return this.text('\n[远端用量] 输入、输出、缓存与合计计数不一致,本轮未记录生成用量;需要远端提供语义一致的真实计数。\n');
|
|
150
|
+
}
|
|
151
|
+
return [{ type: 'usage', usage: { inputTokens: input, outputTokens: output,
|
|
152
|
+
...(read == null ? {} : { cacheReadTokens: read }), ...(write == null ? {} : { cacheWriteTokens: write }),
|
|
153
|
+
...(thought == null ? {} : { reasoningTokens: thought }) } }];
|
|
154
|
+
}
|
|
155
|
+
content(content, reasoning = false) {
|
|
156
|
+
let chunks;
|
|
157
|
+
switch (content.type) {
|
|
158
|
+
case 'text':
|
|
159
|
+
chunks = this.text(content.text, reasoning);
|
|
160
|
+
break;
|
|
161
|
+
case 'image':
|
|
162
|
+
chunks = this.text(`\n[远端图片] 收到 ${content.mimeType} 图片内容;需要持久附件引用才能在官方界面显示,首版未生成附件。${content.uri ? `远端 URI(仅供核查):${plainUri(content.uri)}` : ''}\n`);
|
|
163
|
+
break;
|
|
164
|
+
case 'audio':
|
|
165
|
+
chunks = this.text(`\n[远端音频] 收到 ${content.mimeType} 音频内容;首版尚无持久音频附件适配,需要远端提供文字或可持久引用。\n`);
|
|
166
|
+
break;
|
|
167
|
+
case 'resource_link':
|
|
168
|
+
chunks = this.text(`\n[远端资源] ${content.name}${content.description ? `:${content.description}` : ''};URI(仅供核查,不作为员工设备本地资源打开):${plainUri(content.uri)}\n`);
|
|
169
|
+
break;
|
|
170
|
+
case 'resource':
|
|
171
|
+
chunks = this.text(`\n[远端嵌入资源] URI(仅供核查):${plainUri(content.resource.uri)}\n`);
|
|
172
|
+
if ('text' in content.resource)
|
|
173
|
+
chunks.push(...this.text(content.resource.text));
|
|
174
|
+
else
|
|
175
|
+
chunks.push(...this.text('远端返回二进制资源;首版需要持久附件适配,尚未展示其字节内容。\n'));
|
|
176
|
+
chunks.push(...this.meta(content.resource._meta));
|
|
177
|
+
break;
|
|
178
|
+
default: chunks = this.text(`\n[远端内容] 首版不支持此内容类型:${safeLabel(content.type)};需要对应的公开内容映射。\n`);
|
|
179
|
+
}
|
|
180
|
+
return chunks.concat(this.meta(content._meta));
|
|
181
|
+
}
|
|
182
|
+
toolContent(content) {
|
|
183
|
+
switch (content.type) {
|
|
184
|
+
case 'content': return this.content(content.content).concat(this.meta(content._meta));
|
|
185
|
+
case 'diff': return this.text(`\n[远端文件修改] ${content.path}\n原内容:\n${content.oldText ?? '远端未提供'}\n新内容:\n${content.newText}\n`).concat(this.meta(content._meta));
|
|
186
|
+
case 'terminal': return this.text(`\n[远端终端] ${content.terminalId};首版未创建员工设备本地终端,需要远端提供文本输出。\n`).concat(this.meta(content._meta));
|
|
187
|
+
default: return this.text('\n[远端工具内容] 首版不支持此内容,需要对应的公开显示映射。\n');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
meta(meta, known = []) {
|
|
191
|
+
const fields = record(meta);
|
|
192
|
+
if (!fields)
|
|
193
|
+
return [];
|
|
194
|
+
const names = Object.keys(fields).filter(key => !known.includes(key));
|
|
195
|
+
return names.length ? this.text(`\n[远端扩展] 首版不支持这些扩展字段:${names.map(safeLabel).join('、')};扩展值未展开,需要公开的对应映射。\n`) : [];
|
|
196
|
+
}
|
|
197
|
+
sdk(event) {
|
|
198
|
+
if (event.type === 'turn.completed') {
|
|
199
|
+
const u = event.usage;
|
|
200
|
+
const write = u.cache_write_input_tokens ?? 0;
|
|
201
|
+
const uncached = u.input_tokens - u.cached_input_tokens - write;
|
|
202
|
+
if (![u.input_tokens, u.cached_input_tokens, write, u.output_tokens, uncached].every(n => Number.isSafeInteger(n) && n >= 0))
|
|
203
|
+
throw new NeedError('USAGE_INVALID', '远端用量计数不一致', '有效的 Codex 用量');
|
|
204
|
+
return [{ type: 'usage', usage: { inputTokens: uncached, cacheReadTokens: u.cached_input_tokens, outputTokens: u.output_tokens,
|
|
205
|
+
...(u.cache_write_input_tokens !== undefined ? { cacheWriteTokens: write } : {}),
|
|
206
|
+
...(u.reasoning_output_tokens !== undefined ? { reasoningTokens: u.reasoning_output_tokens } : {}) } }];
|
|
207
|
+
}
|
|
208
|
+
if (event.type !== 'item.started' && event.type !== 'item.updated' && event.type !== 'item.completed')
|
|
209
|
+
return [];
|
|
210
|
+
const item = event.item;
|
|
211
|
+
if (item.type === 'agent_message' || item.type === 'reasoning') {
|
|
212
|
+
const previous = this.seen.get(item.id) ?? '';
|
|
213
|
+
if (!item.text.startsWith(previous))
|
|
214
|
+
throw new NeedError('EVENT_REWRITE_UNSUPPORTED', '远端更改了已输出的文本,当前流不能无损替换', '支持文本替换的展示适配器');
|
|
215
|
+
this.seen.set(item.id, item.text);
|
|
216
|
+
return this.text(item.text.slice(previous.length), item.type === 'reasoning');
|
|
217
|
+
}
|
|
218
|
+
if (item.type === 'command_execution' || item.type === 'file_change' || item.type === 'mcp_tool_call') {
|
|
219
|
+
const key = `tool:${item.id}`;
|
|
220
|
+
if (this.seen.get(key) === item.status)
|
|
221
|
+
return [];
|
|
222
|
+
this.seen.set(key, item.status);
|
|
223
|
+
const title = item.type === 'command_execution' ? item.command : item.type === 'file_change' ? item.changes.map(c => c.path).join(', ') : `${item.server}/${item.tool}`;
|
|
224
|
+
return this.text(`\n[远端工具] ${title} · ${item.status}\n`);
|
|
225
|
+
}
|
|
226
|
+
if (item.type === 'web_search')
|
|
227
|
+
return this.text(`\n[远端搜索] ${item.query}\n`);
|
|
228
|
+
if (item.type === 'todo_list')
|
|
229
|
+
return this.text('\n[远端计划]\n' + item.items.map(i => `${i.completed ? '完成' : '待做'}: ${i.text}`).join('\n') + '\n');
|
|
230
|
+
if (item.type === 'error')
|
|
231
|
+
throw new NeedError('ONESHOT_FAILED', 'Codex 返回了错误,根因未查明', '远端诊断');
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function validCount(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; }
|
|
236
|
+
function validUsageSnapshot(value) {
|
|
237
|
+
const snapshot = record(value);
|
|
238
|
+
if (snapshot?.version !== 1)
|
|
239
|
+
return false;
|
|
240
|
+
if (snapshot.kind === 'zero')
|
|
241
|
+
return Object.keys(snapshot).every(key => key === 'kind' || key === 'version');
|
|
242
|
+
if (snapshot.kind !== 'cumulative' || (snapshot.source !== 'usage' && snapshot.source !== 'quota') || (snapshot.gap !== undefined && snapshot.gap !== true)
|
|
243
|
+
|| !validCount(snapshot.inputTokens) || !validCount(snapshot.outputTokens) || optionalUsageCounters.some(key => snapshot[key] !== undefined && !validCount(snapshot[key])))
|
|
244
|
+
return false;
|
|
245
|
+
const sum = snapshot.inputTokens + snapshot.outputTokens + (snapshot.cachedReadTokens ?? 0) + (snapshot.cachedWriteTokens ?? 0);
|
|
246
|
+
return snapshot.gap === true || Number.isSafeInteger(sum) && (snapshot.totalTokens === undefined || snapshot.totalTokens === sum) && (snapshot.thoughtTokens === undefined || snapshot.thoughtTokens <= snapshot.outputTokens);
|
|
247
|
+
}
|
|
248
|
+
function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined; }
|
|
249
|
+
function safeLabel(value) { return value.replaceAll(/[\r\n\x00-\x1f\x7f]/g, ' '); }
|
|
250
|
+
function plainUri(uri) {
|
|
251
|
+
let result = uri;
|
|
252
|
+
try {
|
|
253
|
+
const parsed = new URL(uri);
|
|
254
|
+
parsed.username = '';
|
|
255
|
+
parsed.password = '';
|
|
256
|
+
parsed.search = '';
|
|
257
|
+
parsed.hash = '';
|
|
258
|
+
result = parsed.toString();
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
result = uri.split(/[?#]/)[0];
|
|
262
|
+
}
|
|
263
|
+
return '`' + safeLabel(result).replaceAll('`', '\u02cb') + '`';
|
|
264
|
+
}
|
|
265
|
+
function toolStatus(status) { return { pending: '待处理', in_progress: '处理中', completed: '完成', failed: '失败' }[status ?? ''] ?? '更新'; }
|
|
266
|
+
function planStatus(status) { return { pending: '待做', in_progress: '进行中', completed: '完成' }[status] ?? safeLabel(status); }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ThreadEvent, type SandboxMode } from '@openai/codex-sdk';
|
|
2
|
+
import { type OneShotRuntime } from './job.ts';
|
|
3
|
+
export type { OneShotRuntime } from './job.ts';
|
|
4
|
+
export interface OneShotOptions {
|
|
5
|
+
wrapperPath: string;
|
|
6
|
+
sshExecutable?: string;
|
|
7
|
+
sandboxMode?: SandboxMode;
|
|
8
|
+
approvalPolicy?: 'never';
|
|
9
|
+
/** Independent disposal signal for control/cleanup; not the already-cancelled turn signal. */
|
|
10
|
+
controlSignal?: AbortSignal;
|
|
11
|
+
onTiming?: (phase: string, elapsedMs: number) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function runOneShot(runtime: OneShotRuntime, options: OneShotOptions, prompt: string, resumeId?: string, signal?: AbortSignal): AsyncGenerator<ThreadEvent>;
|
package/dist/oneshot.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Codex } from '@openai/codex-sdk';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { executeJob } from "./job.js";
|
|
7
|
+
import { NeedError } from "./remote.js";
|
|
8
|
+
export async function* runOneShot(runtime, options, prompt, resumeId, signal) {
|
|
9
|
+
if (!options.sandboxMode || options.approvalPolicy !== 'never')
|
|
10
|
+
throw new NeedError('SETTINGS_REQUIRED', '我需要管理员明确单次模式的沙箱及无交互审批设置', 'oneShotSandbox / oneShotApproval(待 owner 确认)');
|
|
11
|
+
signal?.throwIfAborted();
|
|
12
|
+
const started = performance.now();
|
|
13
|
+
const timing = (phase) => options.onTiming?.(phase, Math.round(performance.now() - started));
|
|
14
|
+
const dir = await mkdtemp(join(tmpdir(), 'aw-x-job-'));
|
|
15
|
+
const spec = { ...runtime, id: randomUUID().replaceAll('-', ''), sshExecutable: options.sshExecutable };
|
|
16
|
+
try {
|
|
17
|
+
timing('prepare-start');
|
|
18
|
+
const prepareSignal = signal && options.controlSignal ? AbortSignal.any([signal, options.controlSignal]) : signal ?? options.controlSignal;
|
|
19
|
+
if (await executeJob(spec, 'prepare', undefined, undefined, false, prepareSignal) !== 0)
|
|
20
|
+
throw new NeedError('ONESHOT_FAILED', '主机单次运行准备失败,根因未查明', '已安装的主机单次运行助手');
|
|
21
|
+
timing('prepare-complete');
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
const file = join(dir, 'job.json');
|
|
24
|
+
await writeFile(file, JSON.stringify(spec), { mode: 0o600 });
|
|
25
|
+
const codex = new Codex({ codexPathOverride: options.wrapperPath, env: { PATH: process.env.PATH ?? '', AW_X_JOB_FILE: file } });
|
|
26
|
+
const threadOptions = { workingDirectory: runtime.cwd, sandboxMode: options.sandboxMode, approvalPolicy: options.approvalPolicy };
|
|
27
|
+
const thread = resumeId ? codex.resumeThread(resumeId, threadOptions) : codex.startThread(threadOptions);
|
|
28
|
+
timing('sdk-start');
|
|
29
|
+
const { events } = await thread.runStreamed(prompt, { signal });
|
|
30
|
+
let first = true;
|
|
31
|
+
let firstModel = true;
|
|
32
|
+
let completed = false;
|
|
33
|
+
for await (const event of events) {
|
|
34
|
+
const lifecycle = event;
|
|
35
|
+
if (lifecycle.type === 'aw.remote.lifecycle') {
|
|
36
|
+
if (lifecycle.phase === 'remote-run-ready' || lifecycle.phase === 'remote-codex-spawned')
|
|
37
|
+
timing(lifecycle.phase);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (first) {
|
|
41
|
+
timing('first-event');
|
|
42
|
+
first = false;
|
|
43
|
+
}
|
|
44
|
+
if (firstModel && (event.type === 'item.started' || event.type === 'item.updated' || event.type === 'item.completed') && (event.item.type === 'agent_message' || event.item.type === 'reasoning')) {
|
|
45
|
+
firstModel = false;
|
|
46
|
+
timing('first-model-output');
|
|
47
|
+
}
|
|
48
|
+
if (event.type === 'turn.failed' || event.type === 'error')
|
|
49
|
+
throw new NeedError('ONESHOT_FAILED', 'Codex 单次运行返回失败,根因未查明', '可用的主机 Codex 会话');
|
|
50
|
+
if (event.type === 'turn.completed')
|
|
51
|
+
completed = true;
|
|
52
|
+
yield event;
|
|
53
|
+
}
|
|
54
|
+
if (!completed)
|
|
55
|
+
throw new NeedError('ONESHOT_INCOMPLETE', '连接结束前未收到 turn.completed,结果不完整', '确认远端运行状态');
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (signal?.aborted)
|
|
59
|
+
throw new NeedError('ABORTED', '本轮单次运行已取消', '重新开始或恢复已保存的线程');
|
|
60
|
+
if (error instanceof NeedError)
|
|
61
|
+
throw error;
|
|
62
|
+
throw new NeedError('ONESHOT_FAILED', 'SSH / Codex 单次运行失败,根因未查明', '有效的远端运行连接');
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
timing('cleanup-start');
|
|
66
|
+
let cleanupOk = false;
|
|
67
|
+
try {
|
|
68
|
+
cleanupOk = (await executeJob(spec, 'kill', undefined, undefined, false, options.controlSignal)) === 0;
|
|
69
|
+
}
|
|
70
|
+
catch { /* No fallback: every failed control attempt below reports cleanup unconfirmed. */ }
|
|
71
|
+
finally {
|
|
72
|
+
await rm(dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
timing('cleanup-complete');
|
|
75
|
+
if (!cleanupOk)
|
|
76
|
+
throw new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '连接中断,无法确认远端进程已结束;需要管理员确认此轮状态', '恢复连接并结束对应运行');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Functional wording from connect-api appendix D1-D3 (L0.5.1 candidate). */
|
|
2
|
+
export declare const REMOTE_CODEX_NAME = "Codex\uFF08\u8FDC\u7A0B\u4E3B\u673A\uFF09";
|
|
3
|
+
export declare const CONNECTION_REQUIRED = "\u8FDC\u7A0B\u4E3B\u673A\u8FDE\u63A5\u7EC4\u4EF6\u6CA1\u6709\u88C5\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
|
|
4
|
+
export declare const TEST_HOST_NOTICE = "\u8FDC\u7A0B\u4E3B\u673A\u6B63\u5728\u4F7F\u7528\u6D4B\u8BD5\u914D\u7F6E\uFF0C\u9047\u5230\u95EE\u9898\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
|
|
5
|
+
/** Employee failures use business language; diagnostic codes remain separately available. */
|
|
6
|
+
export declare function employeeFailure(code: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Functional wording from connect-api appendix D1-D3 (L0.5.1 candidate). */
|
|
2
|
+
export const REMOTE_CODEX_NAME = 'Codex(远程主机)';
|
|
3
|
+
export const CONNECTION_REQUIRED = '远程主机连接组件没有装好,请联系管理员。';
|
|
4
|
+
export const TEST_HOST_NOTICE = '远程主机正在使用测试配置,遇到问题请联系管理员。';
|
|
5
|
+
/** Employee failures use business language; diagnostic codes remain separately available. */
|
|
6
|
+
export function employeeFailure(code) {
|
|
7
|
+
const messages = {
|
|
8
|
+
DEPENDENCY_REQUIRED: CONNECTION_REQUIRED,
|
|
9
|
+
LOGIN_REQUIRED: '需要登录,请到设置 → 远程主机连接 点登录',
|
|
10
|
+
LOGIN_COMPLETED: '登录已完成,请重新发送本轮内容。',
|
|
11
|
+
LOGIN_FAILED: '登录未完成,请到设置 → 远程主机连接 点登录;如仍有问题,请联系管理员。',
|
|
12
|
+
AUTH_STATUS_INVALID: '登录检查没有返回有效结果,请重新检查;如仍有问题,请联系管理员。',
|
|
13
|
+
HOST_OFFLINE: '暂时无法连接远程主机,请联系管理员。',
|
|
14
|
+
CONNECTION_LOST: '远程主机连接已中断,请检查网络后重新连接。',
|
|
15
|
+
SETTINGS_REQUIRED: '远程主机连接尚未设置好,请联系管理员。',
|
|
16
|
+
CONNECT_API_MISMATCH: '远程主机连接组件需要更新,请联系管理员。',
|
|
17
|
+
ENROLLMENT_REQUIRED: '我需要连接资料,请联系管理员发放。',
|
|
18
|
+
NETWORK_KEY_INVALID: '连接资料已经失效,请联系管理员核对并重新发放。',
|
|
19
|
+
EMPLOYEE_MISSING: '远程主机账号还没有开通,请联系管理员。',
|
|
20
|
+
DISPOSED: '远程主机连接已经关闭,请重新连接远程主机。',
|
|
21
|
+
ENROLLMENT_INVALID: '远程主机开通信息需要更新,请联系管理员。',
|
|
22
|
+
HOST_KEY_MISMATCH: '远程主机连接信息需要管理员核对。',
|
|
23
|
+
HOST_TRUST_REQUIRED: '远程主机连接信息还未准备好,请联系管理员。',
|
|
24
|
+
POLICY_DENIED: '这个账号暂时不能使用,请联系公司账号管理员。',
|
|
25
|
+
SESSION_CONTEXT_MISMATCH: '远程主机连接已改变,请重新开始本次检查。',
|
|
26
|
+
ABORTED: '本次检查已取消。',
|
|
27
|
+
CONFIRMATION_REQUIRED: '请先选择是否打开授权窗口。',
|
|
28
|
+
CONTEXT_UNSUPPORTED: '远程 Codex 暂不支持这类会话内容,请联系管理员。',
|
|
29
|
+
CONTENT_UNSUPPORTED: '远程 Codex 暂不支持这类附件,请联系管理员。',
|
|
30
|
+
SYSTEM_PROMPT_UPDATE_PENDING: 'Claude原会话无法保证立即应用已变化的系统或开发者要求,本轮未发送;请新建会话应用新要求。',
|
|
31
|
+
SYSTEM_PROMPT_UNSUPPORTED: '远端没有声明系统与开发者上下文能力,本轮未发送;需要管理员提供支持该能力的远端适配器。',
|
|
32
|
+
SYSTEM_PROMPT_UPDATE_UNSUPPORTED: '远端不能更新原会话的系统与开发者要求,本轮未发送;请新建会话。',
|
|
33
|
+
PROMPT_CONTENT_UNSUPPORTED: '远端没有声明这类输入能力,本轮未发送;请使用受支持的内容或联系管理员。',
|
|
34
|
+
ATTACHMENT_REQUIRED: '我需要官方附件组件读取这张图片,请联系管理员检查组件。',
|
|
35
|
+
ATTACHMENT_INVALID: '图片附件没有返回匹配的已核验内容,本轮未发送;请重新添加图片或联系管理员。',
|
|
36
|
+
ACP_MESSAGE_TOO_LARGE: '本轮输入超过会话消息容量,未截断或发送;请联系管理员调整acpMaxMessageBytes。',
|
|
37
|
+
ACP_CLIENT_LOST: '我需要管理员核对并结束遗留的主机会话;当前保留原进程,没有代答旧审批。',
|
|
38
|
+
ONESHOT_CONTEXT_REQUIRED: '首版单次模式不支持图片或系统与开发者上下文,请改用会话模式。',
|
|
39
|
+
};
|
|
40
|
+
return messages[code] ?? `远程主机暂时无法完成这次操作,请联系管理员。技术详情:诊断代码 ${code}`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
/** Matches the official locale service's English fallback before a client reports. */
|
|
4
|
+
export declare const PROVIDER_NAMES: Readonly<{
|
|
5
|
+
en: "Codex (Remote Host)";
|
|
6
|
+
zh: "Codex(远程主机)";
|
|
7
|
+
}>;
|
|
8
|
+
export type ProviderLanguage = 'en' | 'zh';
|
|
9
|
+
/** Local client presentation only: no host login, model call or locale preference write. */
|
|
10
|
+
export declare class ProviderLocaleRemote extends TypertRemoteService {
|
|
11
|
+
private readonly update;
|
|
12
|
+
private language;
|
|
13
|
+
private disposed;
|
|
14
|
+
constructor(ctx: Context, update: (language: ProviderLanguage) => void);
|
|
15
|
+
setLocale(language: unknown, signal: AbortSignal): Promise<{
|
|
16
|
+
language: ProviderLanguage;
|
|
17
|
+
name: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
36
|
+
import { NeedError } from "./remote.js";
|
|
37
|
+
/** Matches the official locale service's English fallback before a client reports. */
|
|
38
|
+
export const PROVIDER_NAMES = Object.freeze({ en: 'Codex (Remote Host)', zh: 'Codex(远程主机)' });
|
|
39
|
+
/** Local client presentation only: no host login, model call or locale preference write. */
|
|
40
|
+
let ProviderLocaleRemote = (() => {
|
|
41
|
+
let _classSuper = TypertRemoteService;
|
|
42
|
+
let _instanceExtraInitializers = [];
|
|
43
|
+
let _setLocale_decorators;
|
|
44
|
+
return class ProviderLocaleRemote extends _classSuper {
|
|
45
|
+
static {
|
|
46
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
47
|
+
_setLocale_decorators = [Remote];
|
|
48
|
+
__esDecorate(this, null, _setLocale_decorators, { kind: "method", name: "setLocale", static: false, private: false, access: { has: obj => "setLocale" in obj, get: obj => obj.setLocale }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
49
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
50
|
+
}
|
|
51
|
+
update = __runInitializers(this, _instanceExtraInitializers);
|
|
52
|
+
language = 'en';
|
|
53
|
+
disposed = false;
|
|
54
|
+
constructor(ctx, update) {
|
|
55
|
+
super(ctx, 'awCodexPresentation');
|
|
56
|
+
this.update = update;
|
|
57
|
+
ctx.effect(() => () => { this.disposed = true; });
|
|
58
|
+
}
|
|
59
|
+
async setLocale(language, signal) {
|
|
60
|
+
signal.throwIfAborted();
|
|
61
|
+
if (this.disposed)
|
|
62
|
+
throw new NeedError('DISPOSED', 'Codex presentation registration is disposed', 'active plugin');
|
|
63
|
+
if (language !== 'en' && language !== 'zh')
|
|
64
|
+
throw new NeedError('INVALID_REQUEST', 'Unsupported presentation language', 'official en/zh locale');
|
|
65
|
+
if (language !== this.language) {
|
|
66
|
+
this.update(language);
|
|
67
|
+
this.language = language;
|
|
68
|
+
}
|
|
69
|
+
return { language, name: PROVIDER_NAMES[language] };
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
})();
|
|
73
|
+
export { ProviderLocaleRemote };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { AgentApp } from '@agentclientprotocol/sdk';
|
|
2
|
+
import type { AgentApp as ExtendedAgentApp } from '@agentclientprotocol/sdk/unstable';
|
|
3
|
+
import type { ConnectState, ConnectApi as LConnectApi, ConnectionState, LoginPromptState } from '@ai-workbench/connect/api';
|
|
4
|
+
export type { ConnectionState, LoginPromptState } from '@ai-workbench/connect/api';
|
|
5
|
+
import type { OneShotRuntime } from './job.ts';
|
|
6
|
+
import type { BrokerTransport, BrokerSettings } from './broker.ts';
|
|
7
|
+
import type { Interaction, Invocation } from './interaction.ts';
|
|
8
|
+
import { type AgentDefinition } from './agents.ts';
|
|
9
|
+
import type { NativeWorkspaceApi, SessionProjectTarget, WorkspaceNotice, LoginProvider } from './connect-contract.ts';
|
|
10
|
+
export interface Runtime {
|
|
11
|
+
identity: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
acpTransport?: {
|
|
14
|
+
writable: WritableStream<Uint8Array>;
|
|
15
|
+
readable: ReadableStream<Uint8Array>;
|
|
16
|
+
} | AgentApp | ExtendedAgentApp;
|
|
17
|
+
broker?: BrokerTransport;
|
|
18
|
+
oneShot?: OneShotRuntime;
|
|
19
|
+
loginRequired?: () => Promise<void>;
|
|
20
|
+
dispose?: () => Promise<void>;
|
|
21
|
+
binding?: {
|
|
22
|
+
bindingId: string;
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
revision: number;
|
|
25
|
+
};
|
|
26
|
+
connection?: ConnectionState;
|
|
27
|
+
}
|
|
28
|
+
export interface RuntimeRequest extends Invocation {
|
|
29
|
+
mode: 'session' | 'one-shot';
|
|
30
|
+
cwd?: string;
|
|
31
|
+
identity?: string;
|
|
32
|
+
bindingId?: string;
|
|
33
|
+
workspaceId?: string;
|
|
34
|
+
agent?: AgentDefinition;
|
|
35
|
+
retainProcess?: boolean;
|
|
36
|
+
brokerSettings?: Readonly<BrokerSettings>;
|
|
37
|
+
}
|
|
38
|
+
export type RuntimeProvider = ((request: RuntimeRequest) => Promise<Runtime>) & {
|
|
39
|
+
preflight?: (request: RuntimeRequest) => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
export interface ConnectTarget {
|
|
42
|
+
hostContext?: {
|
|
43
|
+
enforcement: 'production' | 'test';
|
|
44
|
+
checkExitCode: 0 | 1;
|
|
45
|
+
failedChecks: {
|
|
46
|
+
id: string;
|
|
47
|
+
status: string;
|
|
48
|
+
}[];
|
|
49
|
+
unverifiedChecks: {
|
|
50
|
+
id: string;
|
|
51
|
+
status: string;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
host: string;
|
|
55
|
+
username: string;
|
|
56
|
+
port: number;
|
|
57
|
+
proxyCommand: string;
|
|
58
|
+
projectRoot: string;
|
|
59
|
+
knownHostsFile: string;
|
|
60
|
+
session: {
|
|
61
|
+
command: string;
|
|
62
|
+
root: string;
|
|
63
|
+
settingsPath: string;
|
|
64
|
+
protocolVersion: 1;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export interface Project {
|
|
68
|
+
name: string;
|
|
69
|
+
path: string;
|
|
70
|
+
}
|
|
71
|
+
type Result<T> = {
|
|
72
|
+
ok: true;
|
|
73
|
+
value: T;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
error: {
|
|
77
|
+
code: string;
|
|
78
|
+
message: string;
|
|
79
|
+
requires: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/** 消费正式connect-api 0.7公开子集;X不响应L登录票据、不直接调用登录。 */
|
|
83
|
+
export interface ConnectApi extends Pick<LConnectApi, 'getConnectionState' | 'getLoginPromptState' | 'requestLoginPrompt' | 'refreshLoginPrompts'>, Partial<NativeWorkspaceApi> {
|
|
84
|
+
getState?(): ConnectState;
|
|
85
|
+
getSshTarget(): Promise<Result<ConnectTarget>>;
|
|
86
|
+
checkLogin(provider: LoginProvider): Promise<Result<{
|
|
87
|
+
provider: string;
|
|
88
|
+
loggedIn: boolean;
|
|
89
|
+
}>>;
|
|
90
|
+
login(provider: LoginProvider): Promise<Result<{
|
|
91
|
+
provider: string;
|
|
92
|
+
loggedIn: boolean;
|
|
93
|
+
}>>;
|
|
94
|
+
listProjects(): Promise<Result<{
|
|
95
|
+
projects: Project[];
|
|
96
|
+
unavailable: {
|
|
97
|
+
name: string;
|
|
98
|
+
reason: string;
|
|
99
|
+
}[];
|
|
100
|
+
}>>;
|
|
101
|
+
createProject(name: string): Promise<Result<Project>>;
|
|
102
|
+
}
|
|
103
|
+
export declare function requireLoginCoordinator(api: ConnectApi): void;
|
|
104
|
+
export declare function requireLoginState(state: LoginPromptState): void;
|
|
105
|
+
declare module '@deepseek-ai/cordis' {
|
|
106
|
+
interface Context {
|
|
107
|
+
aiWorkbenchConnect: ConnectApi;
|
|
108
|
+
}
|
|
109
|
+
interface Events {
|
|
110
|
+
'ai-workbench/connection-state': (state: ConnectionState) => void;
|
|
111
|
+
'ai-workbench/login-prompt-state': (state: LoginPromptState) => void;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Cancel only this consumer's wait; L owns the shared flight and its lifetime. */
|
|
115
|
+
export declare function awaitL<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
116
|
+
export declare function unwrap<T>(result: Result<T>): T;
|
|
117
|
+
export declare function validateTarget(t: ConnectTarget): void;
|
|
118
|
+
/** Only the declared public check identifiers/statuses, never provider evidence. */
|
|
119
|
+
export declare function hostContextNotice(t: ConnectTarget): string | undefined;
|
|
120
|
+
export declare function identity(t: ConnectTarget): string;
|
|
121
|
+
/** Reject draft.3 and legacy runtime rather than inventing workspace bindings. */
|
|
122
|
+
export declare function requireNativeWorkspace(api: ConnectApi): asserts api is ConnectApi & NativeWorkspaceApi;
|
|
123
|
+
/** Authorization stays in L; ensureReady only checks and records the need. */
|
|
124
|
+
export declare function offerLogin(connect: ConnectApi, request: Invocation, currentApi?: () => ConnectApi | undefined, agent?: AgentDefinition): Promise<void>;
|
|
125
|
+
export interface NoticeConsumer {
|
|
126
|
+
reconcile(request: RuntimeRequest, api: ConnectApi): Promise<void>;
|
|
127
|
+
deliver(notice: WorkspaceNotice, resolved: SessionProjectTarget, request: RuntimeRequest, api: ConnectApi): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
export declare function sameTarget(a: ConnectTarget, b: ConnectTarget): boolean;
|
|
130
|
+
export declare function connectProvider(get: () => ConnectApi | undefined, options?: {
|
|
131
|
+
agent?: AgentDefinition;
|
|
132
|
+
ask?: Interaction;
|
|
133
|
+
notice?: NoticeConsumer;
|
|
134
|
+
open?: (target: ConnectTarget, project: Project, request: RuntimeRequest) => Promise<Runtime>;
|
|
135
|
+
}): RuntimeProvider;
|