@bitkyc08/opencodex 2.6.2 → 2.6.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/gui/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-DrtXeL9W.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-BwvDb198.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
4
4
|
"description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/package-main.mjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dev:proxy": "bun run src/cli.ts start",
|
|
37
37
|
"dev:gui": "cd gui && bun run dev",
|
|
38
38
|
"start": "bun run src/cli.ts start",
|
|
39
|
-
"test": "bun test tests",
|
|
39
|
+
"test": "bun test ./tests/",
|
|
40
40
|
"typecheck": "bun x tsc --noEmit",
|
|
41
41
|
"privacy:scan": "bun scripts/privacy-scan.ts",
|
|
42
42
|
"generate:jawcode-metadata": "bun scripts/generate-jawcode-metadata.ts",
|
package/src/adapters/google.ts
CHANGED
|
@@ -13,6 +13,22 @@ import type {
|
|
|
13
13
|
import { isAllowedToolChoice, namespacedToolName, toolAllowedByChoice } from "../types";
|
|
14
14
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Inline image parts (Gemini `inline_data`) extracted from tool-result content. Only base64 data URLs
|
|
18
|
+
* can be inlined; a remote URL has no mime type we can supply, so it is skipped here (the textual
|
|
19
|
+
* result already carries an "[image]" marker via contentPartsToText).
|
|
20
|
+
*/
|
|
21
|
+
function toolResultImageParts(content: string | OcxContentPart[]): unknown[] {
|
|
22
|
+
if (typeof content === "string") return [];
|
|
23
|
+
const parts: unknown[] = [];
|
|
24
|
+
for (const p of content) {
|
|
25
|
+
if (p.type !== "image") continue;
|
|
26
|
+
const data = parseDataUrl(p.imageUrl);
|
|
27
|
+
if (data) parts.push({ inline_data: { mime_type: data.mediaType, data: data.base64 } });
|
|
28
|
+
}
|
|
29
|
+
return parts;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?: unknown; contents: unknown[] } {
|
|
17
33
|
const systemInstruction = parsed.context.systemPrompt?.length
|
|
18
34
|
? { parts: [{ text: parsed.context.systemPrompt.join("\n\n") }] }
|
|
@@ -54,10 +70,15 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
54
70
|
break;
|
|
55
71
|
}
|
|
56
72
|
case "toolResult": {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
// The functionResponse part carries the textual result. Gemini cannot embed images inside a
|
|
74
|
+
// functionResponse, but it does accept sibling inline_data parts in the same user turn, so
|
|
75
|
+
// tool-result screenshots (e.g. Computer Use) ride along as inline_data instead of being
|
|
76
|
+
// flattened to a "[image]" marker the model can't actually see.
|
|
77
|
+
const parts: unknown[] = [
|
|
78
|
+
{ functionResponse: { name: namespacedToolName(msg.toolNamespace, msg.toolName), response: { result: contentPartsToText(msg.content) } } },
|
|
79
|
+
];
|
|
80
|
+
for (const part of toolResultImageParts(msg.content)) parts.push(part);
|
|
81
|
+
contents.push({ role: "user", parts });
|
|
61
82
|
break;
|
|
62
83
|
}
|
|
63
84
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxParsedRequest } from "../types";
|
|
2
|
+
import { namespacedToolName } from "../types";
|
|
2
3
|
|
|
3
4
|
const MAX_KIRO_TOOL_DESCRIPTION = 1024;
|
|
4
5
|
|
|
@@ -14,13 +15,49 @@ function sanitizeKiroSchema(value: unknown): unknown {
|
|
|
14
15
|
return out;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
function ensureRootObjectType(schema: unknown): Record<string, unknown> {
|
|
19
|
+
const obj = schema && typeof schema === "object" && !Array.isArray(schema)
|
|
20
|
+
? schema as Record<string, unknown>
|
|
21
|
+
: {};
|
|
22
|
+
// Bedrock rejects oneOf/allOf/anyOf at the root ("input_schema does not support oneOf, allOf, or
|
|
23
|
+
// anyOf at the top level"). Flatten them into a single object schema by merging every variant's
|
|
24
|
+
// properties so the model can still supply any valid argument. Required is merged only for allOf
|
|
25
|
+
// (AND semantics); anyOf/oneOf (OR) leave required off so a valid single-branch call passes.
|
|
26
|
+
const composition = obj.oneOf ?? obj.anyOf ?? obj.allOf;
|
|
27
|
+
if (Array.isArray(composition)) {
|
|
28
|
+
const merged: Record<string, unknown> = { type: "object" };
|
|
29
|
+
const props: Record<string, unknown> = {};
|
|
30
|
+
const required = new Set<string>();
|
|
31
|
+
for (const variant of composition) {
|
|
32
|
+
if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
|
|
33
|
+
const v = variant as Record<string, unknown>;
|
|
34
|
+
if (v.properties && typeof v.properties === "object") {
|
|
35
|
+
Object.assign(props, sanitizeKiroSchema(v.properties) as Record<string, unknown>);
|
|
36
|
+
}
|
|
37
|
+
if (obj.allOf !== undefined && Array.isArray(v.required)) {
|
|
38
|
+
for (const r of v.required) if (typeof r === "string") required.add(r);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(props).length > 0) merged.properties = props;
|
|
42
|
+
if (required.size > 0) merged.required = [...required];
|
|
43
|
+
return merged;
|
|
44
|
+
}
|
|
45
|
+
const t = obj.type;
|
|
46
|
+
if (t === "object") return obj;
|
|
47
|
+
if (Array.isArray(t) && t.includes("object")) return { ...obj, type: "object" };
|
|
48
|
+
return { ...obj, type: "object" };
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
export function convertKiroToolContext(parsed: OcxParsedRequest): { tools: unknown[]; systemAdditions: string[] } {
|
|
18
52
|
const tools = parsed.context.tools ?? [];
|
|
19
53
|
const systemAdditions: string[] = [];
|
|
20
54
|
return {
|
|
21
55
|
tools: tools.map(t => {
|
|
22
56
|
const description = t.description || `Tool: ${t.name}`;
|
|
23
|
-
|
|
57
|
+
// Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
|
|
58
|
+
// it back unchanged; the bridge's toolNsMap is keyed by this name and restores the MCP namespace
|
|
59
|
+
// Codex routes by. Truncating here breaks long MCP/computer-use round trips.
|
|
60
|
+
const toolName = namespacedToolName(t.namespace, t.name);
|
|
24
61
|
const kiroDescription = description.length > MAX_KIRO_TOOL_DESCRIPTION
|
|
25
62
|
? `Tool documentation moved to the system prompt: ${toolName}.`
|
|
26
63
|
: description;
|
|
@@ -31,7 +68,7 @@ export function convertKiroToolContext(parsed: OcxParsedRequest): { tools: unkno
|
|
|
31
68
|
toolSpecification: {
|
|
32
69
|
name: toolName,
|
|
33
70
|
description: kiroDescription,
|
|
34
|
-
inputSchema: { json: sanitizeKiroSchema(t.parameters ?? {})
|
|
71
|
+
inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(t.parameters ?? {})) },
|
|
35
72
|
},
|
|
36
73
|
};
|
|
37
74
|
}),
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { appendFallbackText, toolCallFallbackText, toolResultFallbackText } from
|
|
|
10
10
|
import { KiroThinkingParser } from "./kiro-thinking";
|
|
11
11
|
import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "./kiro-truncation";
|
|
12
12
|
import { fallbackToolUseId, fingerprint, invocationId, mapModelId, normalizeToolId, osTag, stableConversationId } from "./kiro-wire";
|
|
13
|
+
import { namespacedToolName } from "../types";
|
|
13
14
|
import type {
|
|
14
15
|
AdapterEvent,
|
|
15
16
|
OcxAssistantMessage,
|
|
@@ -55,7 +56,6 @@ interface KiroHistoryEntry {
|
|
|
55
56
|
userInputMessage?: KiroUserInputMessage;
|
|
56
57
|
assistantResponseMessage?: { content: string; toolUses?: KiroToolUse[] };
|
|
57
58
|
}
|
|
58
|
-
|
|
59
59
|
function userContentText(content: string | OcxContentPart[]): string {
|
|
60
60
|
if (typeof content === "string") return content;
|
|
61
61
|
return content.map(p => (p.type === "text" ? p.text : "")).filter(Boolean).join("\n");
|
|
@@ -72,15 +72,12 @@ function usageContentText(content: string | OcxContentPart[]): string {
|
|
|
72
72
|
.filter(Boolean)
|
|
73
73
|
.join("\n");
|
|
74
74
|
}
|
|
75
|
-
|
|
76
75
|
function serializeForUsage(value: unknown): string {
|
|
77
76
|
try { return JSON.stringify(value); } catch { return String(value); }
|
|
78
77
|
}
|
|
79
|
-
|
|
80
78
|
function currentTurnUsageMessages(messages: OcxMessage[]): OcxMessage[] {
|
|
81
79
|
return messages.slice(messages.map(m => m.role).lastIndexOf("assistant") + 1).filter(m => m.role !== "assistant");
|
|
82
80
|
}
|
|
83
|
-
|
|
84
81
|
function currentTurnPayloadMessages(messages: OcxMessage[]): OcxMessage[] {
|
|
85
82
|
const roles = messages.map(m => m.role);
|
|
86
83
|
const lastAssistant = roles.lastIndexOf("assistant");
|
|
@@ -216,12 +213,15 @@ export function buildKiroPayload(parsed: OcxParsedRequest, profileArn: string |
|
|
|
216
213
|
const history: KiroHistoryEntry[] = [];
|
|
217
214
|
const fallbackEntries = new WeakSet<KiroHistoryEntry>();
|
|
218
215
|
let pending: KiroToolResult[] = [];
|
|
216
|
+
let pendingImages: KiroImage[] = [];
|
|
219
217
|
let lastRole = "";
|
|
220
218
|
const attachPending = (entry: KiroHistoryEntry): void => {
|
|
221
219
|
if (pending.length === 0) return;
|
|
222
220
|
const uim = entry.userInputMessage!;
|
|
223
221
|
uim.userInputMessageContext = { ...(uim.userInputMessageContext ?? {}), toolResults: pending };
|
|
222
|
+
if (pendingImages.length > 0) uim.images = [...(uim.images ?? []), ...pendingImages];
|
|
224
223
|
pending = [];
|
|
224
|
+
pendingImages = [];
|
|
225
225
|
};
|
|
226
226
|
const pushUserEntry = (entry: KiroHistoryEntry): void => {
|
|
227
227
|
if (pending.length === 0 && lastRole === "user") {
|
|
@@ -253,7 +253,7 @@ export function buildKiroPayload(parsed: OcxParsedRequest, profileArn: string |
|
|
|
253
253
|
? toolCalls.map(tc => {
|
|
254
254
|
const toolUseId = normalizeToolId(tc.id);
|
|
255
255
|
structuredToolIds.add(toolUseId);
|
|
256
|
-
return { name: tc.name, input: (tc.arguments ?? {}) as Record<string, unknown>, toolUseId };
|
|
256
|
+
return { name: namespacedToolName(tc.namespace, tc.name), input: (tc.arguments ?? {}) as Record<string, unknown>, toolUseId };
|
|
257
257
|
})
|
|
258
258
|
: [];
|
|
259
259
|
if (kiroTools.length === 0) {
|
|
@@ -267,6 +267,7 @@ export function buildKiroPayload(parsed: OcxParsedRequest, profileArn: string |
|
|
|
267
267
|
} else if (msg.role === "toolResult") {
|
|
268
268
|
const tr = msg as OcxToolResultMessage;
|
|
269
269
|
const text = userContentText(tr.content);
|
|
270
|
+
const images = extractKiroImages(tr.content);
|
|
270
271
|
const toolUseId = normalizeToolId(tr.toolCallId);
|
|
271
272
|
if (kiroTools.length > 0 && structuredToolIds.has(toolUseId)) {
|
|
272
273
|
pending.push({
|
|
@@ -274,9 +275,10 @@ export function buildKiroPayload(parsed: OcxParsedRequest, profileArn: string |
|
|
|
274
275
|
status: tr.isError ? "error" : "success",
|
|
275
276
|
toolUseId,
|
|
276
277
|
});
|
|
278
|
+
pendingImages.push(...images);
|
|
277
279
|
} else {
|
|
278
280
|
if (pending.length > 0) pushUserEntry(mkUser("(tool results)"));
|
|
279
|
-
const fallback = mkUser(toolResultFallbackText(tr));
|
|
281
|
+
const fallback = mkUser(toolResultFallbackText(tr), images);
|
|
280
282
|
fallbackEntries.add(fallback);
|
|
281
283
|
pushUserEntry(fallback);
|
|
282
284
|
}
|
|
@@ -440,9 +442,13 @@ export async function* parseKiroStream(
|
|
|
440
442
|
yield contentEvent;
|
|
441
443
|
}
|
|
442
444
|
if (open) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
445
|
+
const input = open.chunks.join("");
|
|
446
|
+
if (!isCompleteKiroToolInput(input)) {
|
|
447
|
+
open = null;
|
|
448
|
+
yield { type: "error", message: kiroTruncationErrorMessage("stream ended before tool stop") };
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
yield* flushTool();
|
|
446
452
|
}
|
|
447
453
|
const outputTokens = estimateTokens(outputChars, modelId);
|
|
448
454
|
const usage: OcxUsage = { inputTokens, outputTokens, estimated: true };
|
package/src/responses/schema.ts
CHANGED
|
@@ -56,7 +56,7 @@ const functionCallItemSchema = z.object({
|
|
|
56
56
|
const functionCallOutputItemSchema = z.object({
|
|
57
57
|
type: z.literal("function_call_output"),
|
|
58
58
|
call_id: z.string().min(1),
|
|
59
|
-
output: z.union([z.string(), z.array(outputContentBlockSchema)]).optional(),
|
|
59
|
+
output: z.union([z.string(), z.array(z.union([outputContentBlockSchema, inputImageBlockSchema]))]).optional(),
|
|
60
60
|
});
|
|
61
61
|
const customToolCallItemSchema = z.object({
|
|
62
62
|
type: z.literal("custom_tool_call"),
|