feat: add URL-driven workspace state and streamed Codex research
This commit is contained in:
@@ -1,82 +1,73 @@
|
||||
import { z } from 'zod'
|
||||
import type { Connection } from '#/features/connections/model'
|
||||
import { mastodonSourceSchema } from '#/features/platforms/mastodon-source'
|
||||
import { InputError } from '#/features/posts/inputs'
|
||||
import { columnSchema, deckSchema, MAX_COLUMNS, newId } from './model'
|
||||
import { z } from "zod";
|
||||
import type { Connection } from "#/features/connections/model";
|
||||
import { mastodonSourceSchema } from "#/features/platforms/mastodon-source";
|
||||
import { InputError } from "#/features/posts/inputs";
|
||||
import { columnSchema, deckSchema, MAX_COLUMNS, newId } from "./model";
|
||||
|
||||
const toolColumn = columnSchema.omit({ id: true, source: true }).extend({
|
||||
id: columnSchema.shape.id
|
||||
.optional()
|
||||
.describe(
|
||||
'Keep an existing column ID when editing; omit for a new column.',
|
||||
),
|
||||
.describe("Keep an existing column ID when editing; omit for a new column."),
|
||||
source: z.union([
|
||||
mastodonSourceSchema,
|
||||
z.discriminatedUnion('kind', [
|
||||
z.discriminatedUnion("kind", [
|
||||
z
|
||||
.object({
|
||||
platform: z.literal('twitter').default('twitter'),
|
||||
kind: z.literal('search'),
|
||||
platform: z.literal("twitter").default("twitter"),
|
||||
kind: z.literal("search"),
|
||||
query: z.string().trim().min(1).max(512),
|
||||
product: z.enum(['Latest', 'Top']).default('Latest'),
|
||||
product: z.enum(["Latest", "Top"]).default("Latest"),
|
||||
following: z.boolean().default(false),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
platform: z.literal('twitter').default('twitter'),
|
||||
kind: z.literal('user'),
|
||||
platform: z.literal("twitter").default("twitter"),
|
||||
kind: z.literal("user"),
|
||||
target: z.string().min(1).max(256),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
platform: z.literal('twitter').default('twitter'),
|
||||
kind: z.literal('list'),
|
||||
platform: z.literal("twitter").default("twitter"),
|
||||
kind: z.literal("list"),
|
||||
target: z.string().min(1).max(256),
|
||||
})
|
||||
.strict(),
|
||||
]),
|
||||
]),
|
||||
})
|
||||
export const emptyToolInput = z.object({}).strict()
|
||||
export const deckIdInput = z.object({ deckId: deckSchema.shape.id }).strict()
|
||||
});
|
||||
export const emptyToolInput = z.object({}).strict();
|
||||
export const deckIdInput = z.object({ deckId: deckSchema.shape.id }).strict();
|
||||
export const deleteDeckToolInput = deckIdInput
|
||||
.extend({ expectedRevision: z.number().int().positive().optional() })
|
||||
.strict()
|
||||
export const getDeckInput = z
|
||||
.object({ deckId: deckSchema.shape.id.optional() })
|
||||
.strict()
|
||||
.strict();
|
||||
export const getDeckInput = z.object({ deckId: deckSchema.shape.id.optional() }).strict();
|
||||
export const setDeckInput = z
|
||||
.object({
|
||||
deckId: deckSchema.shape.id
|
||||
.optional()
|
||||
.describe(
|
||||
'Omit to create a temporary view in this tab; supply an existing ID to replace and activate it.',
|
||||
"Omit to create a temporary view in this tab; supply an existing ID to replace and activate it.",
|
||||
),
|
||||
expectedRevision: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe(
|
||||
'Required when replacing a saved deck; omit for a temporary view.',
|
||||
),
|
||||
.describe("Required when replacing a saved deck; omit for a temporary view."),
|
||||
title: deckSchema.shape.title,
|
||||
columns: z
|
||||
.array(toolColumn)
|
||||
.max(MAX_COLUMNS)
|
||||
.describe(
|
||||
'Complete ordered columns, each bound to a connection ID from list_connections. Omitted existing columns are removed.',
|
||||
"Complete ordered columns, each bound to a connection ID from list_connections. Omitted existing columns are removed.",
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
.strict();
|
||||
|
||||
export function prepareDeck(
|
||||
input: unknown,
|
||||
connections: readonly Connection[],
|
||||
) {
|
||||
const parsed = setDeckInput.parse(input)
|
||||
export function prepareDeck(input: unknown, connections: readonly Connection[]) {
|
||||
const parsed = setDeckInput.parse(input);
|
||||
const deck = deckSchema.parse({
|
||||
id: parsed.deckId ?? newId(),
|
||||
title: parsed.title,
|
||||
@@ -84,45 +75,42 @@ export function prepareDeck(
|
||||
...column,
|
||||
id: column.id ?? newId(),
|
||||
})),
|
||||
})
|
||||
});
|
||||
for (const column of deck.columns) {
|
||||
if (
|
||||
!connections.some(
|
||||
(connection) =>
|
||||
connection.id === column.connectionId &&
|
||||
connection.platform === column.source.platform &&
|
||||
connection.status === 'connected',
|
||||
connection.status === "connected",
|
||||
)
|
||||
)
|
||||
throw new InputError(`Unavailable connection: ${column.connectionId}`)
|
||||
throw new InputError(`Unavailable connection: ${column.connectionId}`);
|
||||
}
|
||||
return deck
|
||||
return deck;
|
||||
}
|
||||
|
||||
export async function toolResult(run: () => unknown | Promise<unknown>) {
|
||||
export async function toolResult(run: () => unknown) {
|
||||
try {
|
||||
return {
|
||||
content: [
|
||||
{ type: 'text' as const, text: JSON.stringify(await run()) },
|
||||
] as const,
|
||||
}
|
||||
content: [{ type: "text" as const, text: JSON.stringify(await run()) }] as const,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
code:
|
||||
error instanceof z.ZodError || error instanceof InputError
|
||||
? 'invalid-input'
|
||||
: 'tool-error',
|
||||
message:
|
||||
error instanceof Error ? error.message : 'Tool execution failed.',
|
||||
? "invalid-input"
|
||||
: "tool-error",
|
||||
message: error instanceof Error ? error.message : "Tool execution failed.",
|
||||
retryable: false,
|
||||
}),
|
||||
},
|
||||
] as const,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user