style: configure prettier (no semicolons, trailing commas)
This commit is contained in:
+98
-122
@@ -1,258 +1,235 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { existsSync, readFileSync } from "node:fs"
|
||||
import { homedir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
|
||||
import type { MessageLike, StopReason, ToolLike } from "./types.ts";
|
||||
import type { MessageLike, StopReason, ToolLike } from "./types.ts"
|
||||
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export function stringValue(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
return typeof value === "string" ? value : undefined
|
||||
}
|
||||
|
||||
function booleanValue(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
return typeof value === "boolean" ? value : undefined
|
||||
}
|
||||
|
||||
export function recordArray(
|
||||
value: unknown,
|
||||
): readonly Record<string, unknown>[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.filter(isRecord);
|
||||
export function recordArray(value: unknown): readonly Record<string, unknown>[] {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.filter(isRecord)
|
||||
}
|
||||
|
||||
export function recordOrEmpty(value: unknown): Record<string, unknown> {
|
||||
if (isRecord(value)) return value;
|
||||
if (isRecord(value)) return value
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(value);
|
||||
if (isRecord(parsed)) return parsed;
|
||||
const parsed: unknown = JSON.parse(value)
|
||||
if (isRecord(parsed)) return parsed
|
||||
} catch {
|
||||
// Some providers stream incomplete JSON argument fragments.
|
||||
}
|
||||
}
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
|
||||
export function numberValue(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value)
|
||||
? value
|
||||
: undefined;
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined
|
||||
}
|
||||
|
||||
function defaultAuthPaths(home: string): string[] {
|
||||
return [
|
||||
join(home, ".commandcode", "auth.json"),
|
||||
join(home, ".pi", "agent", "auth.json"),
|
||||
];
|
||||
return [join(home, ".commandcode", "auth.json"), join(home, ".pi", "agent", "auth.json")]
|
||||
}
|
||||
|
||||
export function getApiKey(
|
||||
options: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
authPaths?: readonly string[];
|
||||
homeDir?: () => string;
|
||||
env?: NodeJS.ProcessEnv
|
||||
authPaths?: readonly string[]
|
||||
homeDir?: () => string
|
||||
} = {},
|
||||
): string | undefined {
|
||||
const env = options.env ?? process.env;
|
||||
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY;
|
||||
const env = options.env ?? process.env
|
||||
if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY
|
||||
|
||||
const home = options.homeDir?.() ?? homedir();
|
||||
const authPaths = options.authPaths ?? defaultAuthPaths(home);
|
||||
const home = options.homeDir?.() ?? homedir()
|
||||
const authPaths = options.authPaths ?? defaultAuthPaths(home)
|
||||
|
||||
for (const authPath of authPaths) {
|
||||
try {
|
||||
if (!existsSync(authPath)) continue;
|
||||
const parsed: unknown = JSON.parse(readFileSync(authPath, "utf-8"));
|
||||
if (!isRecord(parsed)) continue;
|
||||
if (!existsSync(authPath)) continue
|
||||
const parsed: unknown = JSON.parse(readFileSync(authPath, "utf-8"))
|
||||
if (!isRecord(parsed)) continue
|
||||
|
||||
// Legacy: direct apiKey or commandcode field
|
||||
const apiKey = stringValue(parsed.apiKey);
|
||||
if (apiKey) return apiKey;
|
||||
const commandcode = stringValue(parsed.commandcode);
|
||||
if (commandcode) return commandcode;
|
||||
const apiKey = stringValue(parsed.apiKey)
|
||||
if (apiKey) return apiKey
|
||||
const commandcode = stringValue(parsed.commandcode)
|
||||
if (commandcode) return commandcode
|
||||
|
||||
// OAuth: pi stores OAuth credentials as {"commandcode": {"type":"oauth","access":"...","refresh":"...","expires":...}}
|
||||
const providerKey = isRecord(parsed.commandcode)
|
||||
? parsed.commandcode
|
||||
: undefined;
|
||||
const providerKey = isRecord(parsed.commandcode) ? parsed.commandcode : undefined
|
||||
if (providerKey && stringValue(providerKey.type) === "oauth") {
|
||||
const access = stringValue(providerKey.access);
|
||||
if (access) return access;
|
||||
const access = stringValue(providerKey.access)
|
||||
if (access) return access
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed or unreadable auth files.
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function textContent(message: { content?: unknown }): string {
|
||||
return recordArray(message.content)
|
||||
.filter((part) => part.type === "text")
|
||||
.map((part) => stringValue(part.text) ?? "")
|
||||
.join("\n");
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
export function getEnvironmentInfo(): string {
|
||||
return `${process.platform}-${process.arch}, Node.js ${process.version}`;
|
||||
return `${process.platform}-${process.arch}, Node.js ${process.version}`
|
||||
}
|
||||
|
||||
export function toJsonSchema(schema: unknown): unknown {
|
||||
if (!isRecord(schema)) return {};
|
||||
if (!isRecord(schema)) return {}
|
||||
|
||||
const kind = stringValue(schema.kind) ?? stringValue(schema.type);
|
||||
const enumValues = Array.isArray(schema.enum) ? schema.enum : undefined;
|
||||
const kind = stringValue(schema.kind) ?? stringValue(schema.type)
|
||||
const enumValues = Array.isArray(schema.enum) ? schema.enum : undefined
|
||||
if (enumValues) {
|
||||
return { type: typeof enumValues[0], enum: enumValues };
|
||||
return { type: typeof enumValues[0], enum: enumValues }
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case "string":
|
||||
case "String":
|
||||
return { type: "string" };
|
||||
return { type: "string" }
|
||||
case "number":
|
||||
case "Number":
|
||||
return { type: "number" };
|
||||
return { type: "number" }
|
||||
case "boolean":
|
||||
case "Boolean":
|
||||
return { type: "boolean" };
|
||||
return { type: "boolean" }
|
||||
case "object":
|
||||
case "Object": {
|
||||
const properties: Record<string, unknown> = {};
|
||||
const inferredRequired: string[] = [];
|
||||
const sourceProperties = isRecord(schema.properties)
|
||||
? schema.properties
|
||||
: undefined;
|
||||
const properties: Record<string, unknown> = {}
|
||||
const inferredRequired: string[] = []
|
||||
const sourceProperties = isRecord(schema.properties) ? schema.properties : undefined
|
||||
const optional = Array.isArray(schema.optional)
|
||||
? schema.optional.filter(
|
||||
(item): item is string => typeof item === "string",
|
||||
)
|
||||
: [];
|
||||
? schema.optional.filter((item): item is string => typeof item === "string")
|
||||
: []
|
||||
|
||||
if (sourceProperties) {
|
||||
for (const [key, value] of Object.entries(sourceProperties)) {
|
||||
properties[key] = toJsonSchema(value);
|
||||
const valueRecord = isRecord(value) ? value : undefined;
|
||||
if (
|
||||
booleanValue(valueRecord?.optional) !== true &&
|
||||
!optional.includes(key)
|
||||
) {
|
||||
inferredRequired.push(key);
|
||||
properties[key] = toJsonSchema(value)
|
||||
const valueRecord = isRecord(value) ? value : undefined
|
||||
if (booleanValue(valueRecord?.optional) !== true && !optional.includes(key)) {
|
||||
inferredRequired.push(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const explicitRequired = Array.isArray(schema.required)
|
||||
? schema.required.filter(
|
||||
(item): item is string => typeof item === "string",
|
||||
)
|
||||
: undefined;
|
||||
const required = explicitRequired ?? inferredRequired;
|
||||
const out: Record<string, unknown> = { type: "object" };
|
||||
if (Object.keys(properties).length > 0) out.properties = properties;
|
||||
if (required.length > 0) out.required = required;
|
||||
return out;
|
||||
? schema.required.filter((item): item is string => typeof item === "string")
|
||||
: undefined
|
||||
const required = explicitRequired ?? inferredRequired
|
||||
const out: Record<string, unknown> = { type: "object" }
|
||||
if (Object.keys(properties).length > 0) out.properties = properties
|
||||
if (required.length > 0) out.required = required
|
||||
return out
|
||||
}
|
||||
case "array":
|
||||
case "Array":
|
||||
return {
|
||||
type: "array",
|
||||
items: toJsonSchema(schema.items ?? schema.element),
|
||||
};
|
||||
}
|
||||
case "union":
|
||||
case "Union": {
|
||||
const variants = Array.isArray(schema.variants)
|
||||
? schema.variants
|
||||
: Array.isArray(schema.anyOf)
|
||||
? schema.anyOf
|
||||
: [];
|
||||
: []
|
||||
for (const variant of variants) {
|
||||
const converted = toJsonSchema(variant);
|
||||
if (isRecord(converted) && Object.keys(converted).length > 0)
|
||||
return converted;
|
||||
const converted = toJsonSchema(variant)
|
||||
if (isRecord(converted) && Object.keys(converted).length > 0) return converted
|
||||
}
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
case "optional":
|
||||
case "Optional":
|
||||
return toJsonSchema(schema.wrapped ?? schema.inner);
|
||||
return toJsonSchema(schema.wrapped ?? schema.inner)
|
||||
default:
|
||||
return {};
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function toolsToJson(tools?: readonly ToolLike[]): unknown[] {
|
||||
if (!tools) return [];
|
||||
if (!tools) return []
|
||||
return tools.map((tool) => ({
|
||||
type: "function",
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
input_schema: tool.parameters ? toJsonSchema(tool.parameters) : {},
|
||||
}));
|
||||
}))
|
||||
}
|
||||
|
||||
function completeToolCallIds(messages?: readonly MessageLike[]): Set<string> {
|
||||
const callIds = new Set<string>();
|
||||
const resultIds = new Set<string>();
|
||||
const callIds = new Set<string>()
|
||||
const resultIds = new Set<string>()
|
||||
|
||||
for (const message of messages ?? []) {
|
||||
if (message.role === "assistant") {
|
||||
for (const content of recordArray(message.content)) {
|
||||
if (content.type === "toolCall") {
|
||||
const id = stringValue(content.id);
|
||||
if (id) callIds.add(id);
|
||||
const id = stringValue(content.id)
|
||||
if (id) callIds.add(id)
|
||||
}
|
||||
}
|
||||
} else if (message.role === "toolResult") {
|
||||
if (message.toolCallId) resultIds.add(message.toolCallId);
|
||||
if (message.toolCallId) resultIds.add(message.toolCallId)
|
||||
}
|
||||
}
|
||||
|
||||
return new Set([...callIds].filter((id) => resultIds.has(id)));
|
||||
return new Set([...callIds].filter((id) => resultIds.has(id)))
|
||||
}
|
||||
|
||||
export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
|
||||
const out: unknown[] = [];
|
||||
const pairedToolCallIds = completeToolCallIds(messages);
|
||||
const out: unknown[] = []
|
||||
const pairedToolCallIds = completeToolCallIds(messages)
|
||||
|
||||
for (const message of messages ?? []) {
|
||||
if (message.role === "user") {
|
||||
out.push({
|
||||
role: "user",
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? message.content
|
||||
: message.content,
|
||||
});
|
||||
content: typeof message.content === "string" ? message.content : message.content,
|
||||
})
|
||||
} else if (message.role === "assistant") {
|
||||
const parts: unknown[] = [];
|
||||
const parts: unknown[] = []
|
||||
for (const content of recordArray(message.content)) {
|
||||
if (content.type === "text") {
|
||||
parts.push({ type: "text", text: stringValue(content.text) ?? "" });
|
||||
parts.push({ type: "text", text: stringValue(content.text) ?? "" })
|
||||
} else if (content.type === "thinking") {
|
||||
parts.push({
|
||||
type: "reasoning",
|
||||
text: stringValue(content.thinking) ?? "",
|
||||
});
|
||||
})
|
||||
} else if (content.type === "toolCall") {
|
||||
const toolCallId = stringValue(content.id) ?? "";
|
||||
if (!pairedToolCallIds.has(toolCallId)) continue;
|
||||
const toolCallId = stringValue(content.id) ?? ""
|
||||
if (!pairedToolCallIds.has(toolCallId)) continue
|
||||
parts.push({
|
||||
type: "tool-call",
|
||||
toolCallId,
|
||||
toolName: stringValue(content.name) ?? "",
|
||||
input: recordOrEmpty(content.arguments),
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
if (parts.length > 0) out.push({ role: "assistant", content: parts });
|
||||
if (parts.length > 0) out.push({ role: "assistant", content: parts })
|
||||
} else if (message.role === "toolResult") {
|
||||
if (!message.toolCallId || !pairedToolCallIds.has(message.toolCallId))
|
||||
continue;
|
||||
if (!message.toolCallId || !pairedToolCallIds.has(message.toolCallId)) continue
|
||||
out.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
@@ -265,36 +242,35 @@ export function messagesToCC(messages?: readonly MessageLike[]): unknown[] {
|
||||
: { type: "text", value: textContent(message) },
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return out
|
||||
}
|
||||
|
||||
export function parseStreamEventLine(line: string): unknown | undefined {
|
||||
let trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith(":") || trimmed.startsWith("event:"))
|
||||
return undefined;
|
||||
if (trimmed.startsWith("data:")) trimmed = trimmed.slice(5).trim();
|
||||
if (!trimmed || trimmed === "[DONE]") return undefined;
|
||||
let trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith(":") || trimmed.startsWith("event:")) return undefined
|
||||
if (trimmed.startsWith("data:")) trimmed = trimmed.slice(5).trim()
|
||||
if (!trimmed || trimmed === "[DONE]") return undefined
|
||||
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(trimmed);
|
||||
return parsed;
|
||||
const parsed: unknown = JSON.parse(trimmed)
|
||||
return parsed
|
||||
} catch {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function mapFinishReason(reason: unknown): StopReason {
|
||||
if (reason === "tool-calls") return "toolUse";
|
||||
if (reason === "tool-calls") return "toolUse"
|
||||
if (
|
||||
reason === "length" ||
|
||||
reason === "max_tokens" ||
|
||||
reason === "max-tokens" ||
|
||||
reason === "max_output_tokens"
|
||||
) {
|
||||
return "length";
|
||||
return "length"
|
||||
}
|
||||
return "stop";
|
||||
return "stop"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user