@pasko70/pibo 2.5.2 → 3.0.1
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/context/pibo-native-tooling.md +1 -0
- package/dist/agent-runtime/contract.js +2 -1
- package/dist/agent-runtime/history.js +53 -1
- package/dist/agent-runtime/routed-session.js +100 -26
- package/dist/agent-runtimes/codex-native/adapter.js +304 -30
- package/dist/agent-runtimes/codex-native/client.js +22 -3
- package/dist/agent-runtimes/codex-native/first-use.js +294 -0
- package/dist/agent-runtimes/codex-native/history.js +26 -7
- package/dist/agent-runtimes/codex-native/process.js +14 -0
- package/dist/agent-runtimes/codex-native/thread.js +59 -25
- package/dist/agent-runtimes/codex-native/turn.js +27 -0
- package/dist/agent-runtimes/history-proof.js +8 -0
- package/dist/agent-runtimes/omp/adapter.js +61 -14
- package/dist/agent-runtimes/omp/history.js +189 -34
- package/dist/agent-runtimes/omp/thread.js +82 -36
- package/dist/agent-runtimes/pi/adapter.js +30 -3
- package/dist/agent-runtimes/pi/history.js +66 -5
- package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
- package/dist/agent-runtimes/pi/routed-session.js +344 -37
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- package/dist/apps/chat/chat-settings-routes.js +27 -2
- package/dist/apps/chat/chat-speech.js +100 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -5
- package/dist/apps/chat/data/timeline-query-service.js +89 -1
- package/dist/apps/chat/trace-v2.js +210 -8
- package/dist/apps/chat/trace.js +3 -0
- package/dist/apps/chat/web-app.js +108 -3
- package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
- package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
- package/dist/cli.js +49 -5
- package/dist/config/config.js +59 -8
- package/dist/core/preview-server-settings.js +20 -0
- package/dist/core/session-router.js +464 -37
- package/dist/core/user-settings.js +10 -0
- package/dist/data/payload-store.js +57 -1
- package/dist/data/schema.js +18 -1
- package/dist/debug/trace.js +5 -0
- package/dist/gateway/client.js +368 -65
- package/dist/gateway/protocol.js +2 -0
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/web.js +2 -0
- package/dist/index.js +1 -1
- package/dist/loops/store.js +6 -4
- package/dist/mcp/config.js +89 -17
- package/dist/mcp/daemon-client.js +473 -153
- package/dist/mcp/daemon.js +401 -37
- package/dist/plugins/builtin.js +2 -2
- package/dist/plugins/codex-native.js +2 -0
- package/dist/plugins/registry.js +259 -1
- package/dist/previews/cli.js +345 -0
- package/dist/previews/config.js +61 -0
- package/dist/previews/manager.js +644 -0
- package/dist/previews/network.js +198 -0
- package/dist/previews/plugin.js +11 -0
- package/dist/previews/process-supervisor.js +38 -0
- package/dist/previews/proxy.js +387 -0
- package/dist/previews/store.js +750 -0
- package/dist/previews/types.js +1 -0
- package/dist/previews/web-app.js +446 -0
- package/dist/runs/registry.js +22 -0
- package/dist/runs/tools.js +46 -7
- package/dist/session-ui/terminalRows.js +187 -13
- package/dist/sessions/pibo-data-store.js +37 -0
- package/dist/sessions/runtime-binding-persistence.js +34 -0
- package/dist/sessions/runtime-binding.js +6 -0
- package/dist/sessions/sqlite-store.js +37 -0
- package/dist/shared/trace-engine.js +12 -64
- package/dist/shared/trace-event-projection.js +16 -0
- package/dist/shared/trace-history.js +684 -184
- package/dist/shared/trace-limits.js +7 -0
- package/dist/shared/trace-live-patch.js +61 -0
- package/dist/shared/trace-tool-identity.js +43 -0
- package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
- package/dist/speech/openai-codex.js +407 -0
- package/dist/speech/types.js +8 -0
- package/dist/subagents/context.js +5 -3
- package/dist/subagents/observations.js +94 -6
- package/dist/subagents/tool.js +103 -20
- package/dist/tools/codex-image-artifacts.js +24 -0
- package/dist/tools/codex-image-generation.js +4 -22
- package/dist/web/channel.js +88 -4
- package/dist/web/http.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
- package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
package/dist/subagents/tool.js
CHANGED
|
@@ -8,6 +8,7 @@ export const PIBO_AGENT_TOOL_NAMES = [
|
|
|
8
8
|
"pibo_agents_observe",
|
|
9
9
|
"pibo_agents_kill",
|
|
10
10
|
];
|
|
11
|
+
export const PIBO_AGENT_SESSION_NAME_MAX_LENGTH = 40;
|
|
11
12
|
function availableAgentDescription(subagent) {
|
|
12
13
|
return subagent.description?.trim() || `Targets profile ${subagent.targetProfile}.`;
|
|
13
14
|
}
|
|
@@ -30,6 +31,58 @@ export function formatAvailableAgentsForPrompt(subagents) {
|
|
|
30
31
|
function resultText(prefix, value) {
|
|
31
32
|
return `${prefix}\n${JSON.stringify(value, null, 2)}`;
|
|
32
33
|
}
|
|
34
|
+
export function normalizePiboAgentSessionName(value) {
|
|
35
|
+
if (typeof value !== "string")
|
|
36
|
+
throw new Error("Agent session name is required.");
|
|
37
|
+
if ([...value].length > PIBO_AGENT_SESSION_NAME_MAX_LENGTH) {
|
|
38
|
+
throw new Error(`Agent session name must be at most ${PIBO_AGENT_SESSION_NAME_MAX_LENGTH} characters.`);
|
|
39
|
+
}
|
|
40
|
+
const normalized = value.trim();
|
|
41
|
+
if (!normalized)
|
|
42
|
+
throw new Error("Agent session name must not be empty.");
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
function preparePiboAgentSessionNameInput(input) {
|
|
46
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
47
|
+
throw new Error("Agent session name is required.");
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...input,
|
|
51
|
+
sessionName: normalizePiboAgentSessionName(input.sessionName),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function preparePiboAgentToolInput(input) {
|
|
55
|
+
return preparePiboAgentSessionNameInput(input);
|
|
56
|
+
}
|
|
57
|
+
function preparePiboDeprecatedSubagentToolInput(input) {
|
|
58
|
+
return preparePiboAgentSessionNameInput(input);
|
|
59
|
+
}
|
|
60
|
+
export function formatAgentObservationsForModel(result) {
|
|
61
|
+
const includeTools = result.filters.includeTools === true;
|
|
62
|
+
const toolDetail = result.filters.toolDetail ?? "summary";
|
|
63
|
+
const lines = [
|
|
64
|
+
`Agent observations (${result.observations.length}; tools=${includeTools ? toolDetail : "hidden"}; order=${result.filters.order ?? "desc"}; limit=${result.filters.limit ?? 20})`,
|
|
65
|
+
`nextAfterSequence=${result.nextAfterSequence}; truncated=${result.truncated}`,
|
|
66
|
+
];
|
|
67
|
+
if (result.observations.length === 0) {
|
|
68
|
+
lines.push("", "No delegated-agent observations matched the filters.");
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
for (const observation of result.observations) {
|
|
72
|
+
const scope = [
|
|
73
|
+
observation.name,
|
|
74
|
+
observation.threadKey ? `thread=${observation.threadKey}` : undefined,
|
|
75
|
+
observation.requestId ? `request=${observation.requestId}` : undefined,
|
|
76
|
+
].filter(Boolean).join("; ");
|
|
77
|
+
const event = observation.kind === "tool"
|
|
78
|
+
? `${observation.eventType}${observation.toolName ? ` ${observation.toolName}` : ""}`
|
|
79
|
+
: observation.eventType;
|
|
80
|
+
lines.push("", `#${observation.sequence} ${scope} — ${event}${observation.isError ? " [error]" : ""}`);
|
|
81
|
+
if (observation.text)
|
|
82
|
+
lines.push(observation.text);
|
|
83
|
+
}
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
}
|
|
33
86
|
function normalizeAgentSendMessageResult(result, fallbackRequestId) {
|
|
34
87
|
return {
|
|
35
88
|
...result,
|
|
@@ -55,20 +108,27 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
55
108
|
name: "pibo_agents_send_message",
|
|
56
109
|
title: "Pibo Agents Send Message",
|
|
57
110
|
description: [
|
|
58
|
-
"Yielded-only delegated send. Start this tool through pibo_run_start; bounded waits do not limit the child lifetime.",
|
|
111
|
+
"Yielded-only delegated send with a required sessionName. It must be a nonblank string of at most 40 Unicode code points and is trimmed before use. name selects the configured agent, sessionName is the human-readable child-session title, and threadKey controls conversation reuse. Invalid arguments fail before a run or child session is created. Start this tool through pibo_run_start; bounded waits do not limit the child lifetime.",
|
|
59
112
|
"Available agents:",
|
|
60
113
|
catalog,
|
|
61
114
|
].join("\n"),
|
|
62
|
-
promptSnippet: "Start pibo_agents_send_message through pibo_run_start. Reuse threadKey to continue its child session, and use run wait/status/read/cancel plus agent observe for lifecycle control.",
|
|
115
|
+
promptSnippet: "Start pibo_agents_send_message through pibo_run_start. Provide a nonblank sessionName of at most 40 Unicode code points on every call; Pibo trims it and rejects invalid input before creating a run. Follow-up calls update the reused child title without changing identity. Reuse threadKey to continue its child session, and use run wait/status/read/cancel plus agent observe for lifecycle control.",
|
|
63
116
|
executionMode: "parallel",
|
|
64
117
|
inputSchema: Type.Object({
|
|
65
|
-
name: piboStringEnum(names, { description: "
|
|
118
|
+
name: piboStringEnum(names, { description: "Configured delegated-agent selector; not the child title or reuse key" }),
|
|
119
|
+
sessionName: Type.String({
|
|
120
|
+
description: "Required nonblank human-readable child Pibo Session title, trimmed before use and limited to 40 Unicode code points. Follow-up calls update the title without changing thread identity.",
|
|
121
|
+
minLength: 1,
|
|
122
|
+
maxLength: PIBO_AGENT_SESSION_NAME_MAX_LENGTH,
|
|
123
|
+
pattern: "\\S",
|
|
124
|
+
}),
|
|
66
125
|
message: Type.String({ description: "Message to send to the delegated agent" }),
|
|
67
126
|
threadKey: Type.Optional(Type.String({
|
|
68
|
-
description: "Stable key for
|
|
127
|
+
description: "Stable reuse key for one delegated-agent conversation; independent of sessionName. Omit it to create a new child session.",
|
|
69
128
|
maxLength: 256,
|
|
70
129
|
})),
|
|
71
130
|
}),
|
|
131
|
+
prepareInput: preparePiboAgentToolInput,
|
|
72
132
|
async execute(toolCallId, params, signal, _onUpdate, context) {
|
|
73
133
|
const subagent = byName.get(params.name);
|
|
74
134
|
if (!subagent)
|
|
@@ -76,10 +136,12 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
76
136
|
if (!context.yieldedRunId) {
|
|
77
137
|
throw new Error("pibo_agents_send_message is yielded-only. Start it through pibo_run_start.");
|
|
78
138
|
}
|
|
139
|
+
const preparedParams = preparePiboAgentToolInput(params);
|
|
79
140
|
const result = normalizeAgentSendMessageResult(await controller.sendMessage({
|
|
80
141
|
subagent,
|
|
81
|
-
|
|
82
|
-
|
|
142
|
+
sessionName: preparedParams.sessionName,
|
|
143
|
+
message: preparedParams.message,
|
|
144
|
+
threadKey: preparedParams.threadKey,
|
|
83
145
|
toolCallId,
|
|
84
146
|
requestId: context.yieldedRunId,
|
|
85
147
|
parentProvenance: context.getActiveMessage?.()?.provenance,
|
|
@@ -105,8 +167,8 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
105
167
|
definePiboTool({
|
|
106
168
|
name: "pibo_agents_list_agents",
|
|
107
169
|
title: "Pibo Agents List Agents",
|
|
108
|
-
description: "List available delegated-agent profiles and child agent instances owned by this session.",
|
|
109
|
-
promptSnippet: "List available delegated agents and existing child instances with their agentId,
|
|
170
|
+
description: "List available delegated-agent profiles and child agent instances owned by this session, including each current sessionName when available.",
|
|
171
|
+
promptSnippet: "List available delegated agents and existing child instances with their agentId, current sessionName, threadKey, profile, and running, idle, or killed status.",
|
|
110
172
|
executionMode: "parallel",
|
|
111
173
|
annotations: { readOnly: true },
|
|
112
174
|
inputSchema: Type.Object({}),
|
|
@@ -121,8 +183,12 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
121
183
|
definePiboTool({
|
|
122
184
|
name: "pibo_agents_observe",
|
|
123
185
|
title: "Pibo Agents Observe",
|
|
124
|
-
description:
|
|
125
|
-
|
|
186
|
+
description: [
|
|
187
|
+
"Read completed delegated-agent messages with bounded cursor, identity, event, time, text, order, and limit filters.",
|
|
188
|
+
"Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.",
|
|
189
|
+
"Set includeTools=true to add compact tool calls and terminal results; set toolDetail=full only for bounded diagnostic inspection.",
|
|
190
|
+
].join("\n"),
|
|
191
|
+
promptSnippet: "Observe child-agent progress through completed assistant messages. Defaults: newest 20 messages, no streaming deltas, no duplicate tool progress events, no tools. Set includeTools=true for compact tool call/result summaries, toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. Pass afterSequence from the previous result for cursor polling; cursor pages consume the oldest unseen matches even when order is desc.",
|
|
126
192
|
executionMode: "parallel",
|
|
127
193
|
annotations: { readOnly: true },
|
|
128
194
|
inputSchema: Type.Object({
|
|
@@ -130,21 +196,23 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
130
196
|
agentIds: Type.Optional(Type.Array(Type.String({ description: "Owned child agentId" }), { maxItems: 50 })),
|
|
131
197
|
names: Type.Optional(Type.Array(piboStringEnum(names), { maxItems: 50 })),
|
|
132
198
|
threadKeys: Type.Optional(Type.Array(Type.String(), { maxItems: 50 })),
|
|
133
|
-
eventTypes: Type.Optional(Type.Array(Type.String({ description: "Exact Pibo output event type" }), { maxItems: 50 })),
|
|
134
|
-
kinds: Type.Optional(Type.Array(piboStringEnum(["message", "thinking", "tool", "error", "lifecycle", "event"]), { maxItems: 6 })),
|
|
199
|
+
eventTypes: Type.Optional(Type.Array(Type.String({ description: "Exact Pibo output event type. Explicit filters can retrieve progress events hidden by the default view." }), { maxItems: 50 })),
|
|
200
|
+
kinds: Type.Optional(Type.Array(piboStringEnum(["message", "thinking", "tool", "error", "lifecycle", "event"], { description: "Optional broad event kinds, including progress events. Omit eventTypes and kinds for the compact default view." }), { maxItems: 6 })),
|
|
135
201
|
roles: Type.Optional(Type.Array(Type.String({ description: "Exact normalized role, for example assistant" }), { maxItems: 20 })),
|
|
136
202
|
since: Type.Optional(Type.String({ description: "Inclusive ISO-8601 lower timestamp bound" })),
|
|
137
203
|
until: Type.Optional(Type.String({ description: "Inclusive ISO-8601 upper timestamp bound" })),
|
|
138
204
|
textContains: Type.Optional(Type.String({ description: "Case-insensitive substring match against normalized observation text" })),
|
|
139
|
-
afterSequence: Type.Optional(Type.Integer({ description: "Exclusive live observation cursor. Cursor pages consume the oldest unseen
|
|
140
|
-
order: Type.Optional(piboStringEnum(["asc", "desc"], { default: "
|
|
141
|
-
limit: Type.Optional(Type.Integer({ description: "Maximum
|
|
142
|
-
|
|
205
|
+
afterSequence: Type.Optional(Type.Integer({ description: "Exclusive live observation cursor. Cursor pages consume the oldest unseen matches; desc reverses only the returned page.", minimum: 0 })),
|
|
206
|
+
order: Type.Optional(piboStringEnum(["asc", "desc"], { default: "desc", description: "Newest first by default when no cursor is supplied" })),
|
|
207
|
+
limit: Type.Optional(Type.Integer({ description: "Maximum completed messages or activity records to return. Use 50 explicitly when needed.", minimum: 1, maximum: 200, default: 20 })),
|
|
208
|
+
includeTools: Type.Optional(Type.Boolean({ description: "Include tools. Default false. With the default message view, true adds tool_call and tool_execution_finished records.", default: false })),
|
|
209
|
+
toolDetail: Type.Optional(piboStringEnum(["summary", "full"], { default: "summary", description: "Tool text detail when tools are included. summary is compact; full remains bounded to the observation text limit." })),
|
|
210
|
+
includeDetails: Type.Optional(Type.Boolean({ description: "Include the normalized source event in structured details. Default false; use only for diagnostics.", default: false })),
|
|
143
211
|
}),
|
|
144
212
|
async execute(_toolCallId, params) {
|
|
145
213
|
const result = controller.observe(params);
|
|
146
214
|
return {
|
|
147
|
-
content: [{ type: "text", text:
|
|
215
|
+
content: [{ type: "text", text: formatAgentObservationsForModel(result) }],
|
|
148
216
|
details: result,
|
|
149
217
|
};
|
|
150
218
|
},
|
|
@@ -198,18 +266,33 @@ export function createSubagentToolDefinitions(subagents, runner) {
|
|
|
198
266
|
definitions.push(definePiboTool({
|
|
199
267
|
name: toolName,
|
|
200
268
|
title: `Pibo Subagent ${subagent.name}`,
|
|
201
|
-
description: subagent.description ?? `Send a message to the ${subagent.name} subagent
|
|
202
|
-
promptSnippet: subagent.description ?? `Send a message to the ${subagent.name} subagent
|
|
269
|
+
description: `${subagent.description ?? `Send a message to the ${subagent.name} subagent.`} sessionName is required, trimmed, nonblank, and limited to 40 Unicode code points.`,
|
|
270
|
+
promptSnippet: `Provide a nonblank sessionName of at most 40 Unicode code points. ${subagent.description ?? `Send a message to the ${subagent.name} subagent.`} Pass the same threadKey when you want to continue the same subagent session.`,
|
|
203
271
|
executionMode: "parallel",
|
|
204
272
|
inputSchema: Type.Object({
|
|
273
|
+
sessionName: Type.String({
|
|
274
|
+
description: "Required human-readable delegated-session title, trimmed before use and limited to 40 Unicode code points.",
|
|
275
|
+
minLength: 1,
|
|
276
|
+
maxLength: PIBO_AGENT_SESSION_NAME_MAX_LENGTH,
|
|
277
|
+
pattern: "\\S",
|
|
278
|
+
}),
|
|
205
279
|
message: Type.String({ description: "Message to send to the subagent" }),
|
|
206
280
|
threadKey: Type.Optional(Type.String({
|
|
207
281
|
description: "Stable key for continuing a previous subagent conversation. Omit it to create a new subagent session.",
|
|
208
282
|
maxLength: 256,
|
|
209
283
|
})),
|
|
210
284
|
}),
|
|
285
|
+
prepareInput: preparePiboDeprecatedSubagentToolInput,
|
|
211
286
|
async execute(toolCallId, params, signal) {
|
|
212
|
-
const
|
|
287
|
+
const preparedParams = preparePiboDeprecatedSubagentToolInput(params);
|
|
288
|
+
const result = await runner.runSubagent({
|
|
289
|
+
subagent,
|
|
290
|
+
sessionName: preparedParams.sessionName,
|
|
291
|
+
message: preparedParams.message,
|
|
292
|
+
threadKey: preparedParams.threadKey,
|
|
293
|
+
toolCallId,
|
|
294
|
+
signal,
|
|
295
|
+
});
|
|
213
296
|
return { content: [{ type: "text", text: result.reply.text }], details: result };
|
|
214
297
|
},
|
|
215
298
|
}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getPiboHome } from "../core/pibo-home.js";
|
|
4
|
+
export function codexImageArtifactRoot() {
|
|
5
|
+
return join(getPiboHome(), "generated_images");
|
|
6
|
+
}
|
|
7
|
+
export function codexImageArtifactPath(sessionId, toolCallId) {
|
|
8
|
+
const safeSessionId = sanitizePathPart(sessionId?.trim() || "local");
|
|
9
|
+
const safeToolCallId = sanitizePathPart(toolCallId || `image_${Date.now()}`);
|
|
10
|
+
const artifactId = `${safeSessionId}/${safeToolCallId}.png`;
|
|
11
|
+
return {
|
|
12
|
+
artifactId,
|
|
13
|
+
savedPath: join(codexImageArtifactRoot(), safeSessionId, `${safeToolCallId}.png`),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function saveCodexGeneratedImage(sessionId, toolCallId, b64Json) {
|
|
17
|
+
const target = codexImageArtifactPath(sessionId, toolCallId);
|
|
18
|
+
await mkdir(dirname(target.savedPath), { recursive: true });
|
|
19
|
+
await writeFile(target.savedPath, Buffer.from(b64Json.trim(), "base64"));
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
22
|
+
function sanitizePathPart(value) {
|
|
23
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 120) || "unknown";
|
|
24
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { platform, release, arch } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import { extname, isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { readPiCredential, resolvePiProviderAuth, } from "../agent-runtimes/pi/credentials.js";
|
|
6
6
|
import { definePiboTool } from "./contract.js";
|
|
7
|
-
import {
|
|
7
|
+
import { saveCodexGeneratedImage } from "./codex-image-artifacts.js";
|
|
8
8
|
const OPENAI_CODEX_PROVIDER = "openai-codex";
|
|
9
9
|
const DEFAULT_CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api";
|
|
10
10
|
const CODEX_IMAGE_MODEL = "gpt-image-2";
|
|
@@ -141,24 +141,6 @@ function validateImageArgs(params) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
function sanitizePathPart(value) {
|
|
145
|
-
return value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 120) || "unknown";
|
|
146
|
-
}
|
|
147
|
-
function artifactPath(sessionId, toolCallId) {
|
|
148
|
-
const safeSessionId = sanitizePathPart(sessionId?.trim() || "local");
|
|
149
|
-
const safeToolCallId = sanitizePathPart(toolCallId || `image_${Date.now()}`);
|
|
150
|
-
const artifactId = `${safeSessionId}/${safeToolCallId}.png`;
|
|
151
|
-
return {
|
|
152
|
-
artifactId,
|
|
153
|
-
savedPath: join(getPiboHome(), "generated_images", safeSessionId, `${safeToolCallId}.png`),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
async function saveGeneratedImage(sessionId, toolCallId, b64Json) {
|
|
157
|
-
const target = artifactPath(sessionId, toolCallId);
|
|
158
|
-
await mkdir(dirname(target.savedPath), { recursive: true });
|
|
159
|
-
await writeFile(target.savedPath, Buffer.from(b64Json.trim(), "base64"));
|
|
160
|
-
return target;
|
|
161
|
-
}
|
|
162
144
|
function truncateErrorBody(text) {
|
|
163
145
|
const compact = text.replace(/\s+/g, " ").trim();
|
|
164
146
|
return compact.length > 1000 ? `${compact.slice(0, 1000)}…` : compact;
|
|
@@ -240,7 +222,7 @@ export function createCodexImageGenerationToolDefinition(context = {}, options =
|
|
|
240
222
|
const auth = await getCodexImageAuth();
|
|
241
223
|
const response = await postCodexImageRequest(endpoint, createRequest(params.prompt, images), auth, signal, options.baseUrl);
|
|
242
224
|
const result = firstImageB64(response);
|
|
243
|
-
const saved = await
|
|
225
|
+
const saved = await saveCodexGeneratedImage(context.piboSessionId, toolCallId, result);
|
|
244
226
|
const details = {
|
|
245
227
|
provider: OPENAI_CODEX_PROVIDER,
|
|
246
228
|
api: "codex-chatgpt-images",
|
package/dist/web/channel.js
CHANGED
|
@@ -40,6 +40,43 @@ function firstHeaderValue(value) {
|
|
|
40
40
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
41
41
|
return raw?.split(",")[0]?.trim() || undefined;
|
|
42
42
|
}
|
|
43
|
+
function strictAuthorityHostname(rawAuthority) {
|
|
44
|
+
if (!rawAuthority)
|
|
45
|
+
return undefined;
|
|
46
|
+
const authority = rawAuthority.trim();
|
|
47
|
+
if (!authority || authority.includes(",") || /[\\/@?#\s]/.test(authority))
|
|
48
|
+
return undefined;
|
|
49
|
+
try {
|
|
50
|
+
return new URL(`http://${authority}`).hostname.toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function rawRequestHostname(request) {
|
|
57
|
+
const rawAuthority = request.headers.host;
|
|
58
|
+
if (Array.isArray(rawAuthority))
|
|
59
|
+
return undefined;
|
|
60
|
+
const directHostname = strictAuthorityHostname(rawAuthority);
|
|
61
|
+
if (!directHostname)
|
|
62
|
+
return undefined;
|
|
63
|
+
const forwardedHost = request.headers["x-forwarded-host"];
|
|
64
|
+
if (Array.isArray(forwardedHost) || forwardedHost?.includes(","))
|
|
65
|
+
return undefined;
|
|
66
|
+
const forwardedProto = request.headers["x-forwarded-proto"];
|
|
67
|
+
if (Array.isArray(forwardedProto) || forwardedProto?.includes(","))
|
|
68
|
+
return undefined;
|
|
69
|
+
if (Boolean(forwardedHost) !== Boolean(forwardedProto))
|
|
70
|
+
return undefined;
|
|
71
|
+
if (forwardedProto && forwardedProto !== "http" && forwardedProto !== "https")
|
|
72
|
+
return undefined;
|
|
73
|
+
if (isLoopbackAddress(request.socket.remoteAddress) &&
|
|
74
|
+
forwardedHost &&
|
|
75
|
+
forwardedProto) {
|
|
76
|
+
return strictAuthorityHostname(forwardedHost);
|
|
77
|
+
}
|
|
78
|
+
return directHostname;
|
|
79
|
+
}
|
|
43
80
|
function isLoopbackAddress(address) {
|
|
44
81
|
return address === "::1" || address === "127.0.0.1" || address?.startsWith("127.") === true || address?.startsWith("::ffff:127.") === true;
|
|
45
82
|
}
|
|
@@ -240,9 +277,28 @@ export function createWebHostChannel(options = {}) {
|
|
|
240
277
|
}
|
|
241
278
|
};
|
|
242
279
|
const handleRequest = async (nodeRequest, nodeResponse) => {
|
|
280
|
+
const requestController = new AbortController();
|
|
281
|
+
const abortRequest = () => requestController.abort(new Error("HTTP client disconnected"));
|
|
282
|
+
const abortPrematureResponse = () => {
|
|
283
|
+
if (!nodeResponse.writableFinished)
|
|
284
|
+
abortRequest();
|
|
285
|
+
};
|
|
286
|
+
nodeRequest.once("aborted", abortRequest);
|
|
287
|
+
nodeResponse.once("close", abortPrematureResponse);
|
|
243
288
|
try {
|
|
244
289
|
const baseURL = createRequestBaseURL(nodeRequest, host, port, options.canonicalBaseURL);
|
|
245
|
-
const
|
|
290
|
+
const requestURL = new URL(nodeRequest.url ?? "/", baseURL);
|
|
291
|
+
const requestHostname = rawRequestHostname(nodeRequest);
|
|
292
|
+
const ctx = requireContext();
|
|
293
|
+
const apps = ctx.getWebApps();
|
|
294
|
+
const hostApp = requestHostname === requestURL.hostname.toLowerCase()
|
|
295
|
+
? apps.find((candidate) => candidate.matchesHost?.(requestHostname))
|
|
296
|
+
: undefined;
|
|
297
|
+
if (hostApp?.handleNodeRequest) {
|
|
298
|
+
await hostApp.handleNodeRequest(nodeRequest, nodeResponse, createAppContext(ctx), requestURL);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const baseRequest = await nodeRequestToWebRequest(nodeRequest, baseURL, requestController.signal);
|
|
246
302
|
// Inject the TCP socket peer into every request so the local auth
|
|
247
303
|
// plugin can apply the same loopback predicate from `getSession`
|
|
248
304
|
// regardless of whether the call came from a browser cookie, the
|
|
@@ -271,13 +327,11 @@ export function createWebHostChannel(options = {}) {
|
|
|
271
327
|
await sendResponse(nodeResponse, authResponse);
|
|
272
328
|
return;
|
|
273
329
|
}
|
|
274
|
-
const ctx = requireContext();
|
|
275
330
|
const simpleApiResponse = await handleSimpleAgentApiRequest(request, ctx);
|
|
276
331
|
if (simpleApiResponse) {
|
|
277
332
|
await sendResponse(nodeResponse, simpleApiResponse);
|
|
278
333
|
return;
|
|
279
334
|
}
|
|
280
|
-
const apps = ctx.getWebApps();
|
|
281
335
|
const app = apps.find((candidate) => matchPrefix(url.pathname, candidate.mountPath) || matchPrefix(url.pathname, candidate.apiPrefix));
|
|
282
336
|
if (app) {
|
|
283
337
|
const response = await app.handleRequest(request, createAppContext(ctx));
|
|
@@ -302,7 +356,34 @@ export function createWebHostChannel(options = {}) {
|
|
|
302
356
|
}
|
|
303
357
|
catch (error) {
|
|
304
358
|
const status = error instanceof PiboAuthError || error instanceof PiboWebHttpError ? error.statusCode : 500;
|
|
305
|
-
|
|
359
|
+
if (!nodeResponse.destroyed) {
|
|
360
|
+
await sendResponse(nodeResponse, responseJson({ error: error instanceof Error ? error.message : String(error) }, { status }));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
nodeRequest.removeListener("aborted", abortRequest);
|
|
365
|
+
nodeResponse.removeListener("close", abortPrematureResponse);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const handleUpgrade = async (nodeRequest, socket, head) => {
|
|
369
|
+
try {
|
|
370
|
+
const ctx = requireContext();
|
|
371
|
+
const requestURL = new URL(nodeRequest.url ?? "/", createRequestBaseURL(nodeRequest, host, port, options.canonicalBaseURL));
|
|
372
|
+
const requestHostname = rawRequestHostname(nodeRequest);
|
|
373
|
+
const app = requestHostname === requestURL.hostname.toLowerCase()
|
|
374
|
+
? ctx.getWebApps().find((candidate) => candidate.matchesHost?.(requestHostname))
|
|
375
|
+
: undefined;
|
|
376
|
+
if (!app?.handleUpgrade) {
|
|
377
|
+
socket.end("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
await app.handleUpgrade(nodeRequest, socket, head, createAppContext(ctx), requestURL);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (!socket.destroyed) {
|
|
384
|
+
const unauthorized = error instanceof PiboAuthError || error instanceof PiboWebHttpError;
|
|
385
|
+
socket.end(`HTTP/1.1 ${unauthorized ? 401 : 502} ${unauthorized ? "Unauthorized" : "Bad Gateway"}\r\nConnection: close\r\n\r\n`);
|
|
386
|
+
}
|
|
306
387
|
}
|
|
307
388
|
};
|
|
308
389
|
return {
|
|
@@ -320,6 +401,9 @@ export function createWebHostChannel(options = {}) {
|
|
|
320
401
|
server = createServer((request, response) => {
|
|
321
402
|
void handleRequest(request, response);
|
|
322
403
|
});
|
|
404
|
+
server.on("upgrade", (request, socket, head) => {
|
|
405
|
+
void handleUpgrade(request, socket, head);
|
|
406
|
+
});
|
|
323
407
|
server.on("connection", (socket) => {
|
|
324
408
|
sockets.add(socket);
|
|
325
409
|
socket.once("close", () => sockets.delete(socket));
|
package/dist/web/http.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function readJsonBody(request) {
|
|
|
44
44
|
throw new PiboWebHttpError("Invalid JSON body", 400);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
export async function nodeRequestToWebRequest(request, baseURL) {
|
|
47
|
+
export async function nodeRequestToWebRequest(request, baseURL, signal) {
|
|
48
48
|
const url = new URL(request.url ?? "/", baseURL);
|
|
49
49
|
const headers = new Headers();
|
|
50
50
|
for (const [key, value] of Object.entries(request.headers)) {
|
|
@@ -74,6 +74,7 @@ export async function nodeRequestToWebRequest(request, baseURL) {
|
|
|
74
74
|
method: request.method,
|
|
75
75
|
headers,
|
|
76
76
|
body,
|
|
77
|
+
signal,
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
80
|
export async function sendWebResponse(response, webResponse, options = {}) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "3.0.1",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|