Skip to main content

typeDefs

@forge/monorepo


@forge/monorepo / backend/src / typeDefs

Variable: typeDefs

const typeDefs: "\n scalar JSON\n scalar DateTime\n\n type Conversation {\n id: ID!\n title: String!\n version: Int!\n archivedAt: DateTime\n createdAt: DateTime!\n updatedAt: DateTime!\n }\n\n type ConversationPage {\n items: [Conversation!]!\n nextCursor: String\n }\n\n enum RunStatus {\n queued\n running\n waiting_for_question\n waiting_for_approval\n retry_pending\n completed\n failed\n cancelled\n }\n\n type Run {\n id: ID!\n "Null for a run that belongs to no conversation — a triggered automation rather than a chat turn (#198)."\n conversationId: ID\n status: RunStatus!\n createdAt: DateTime!\n finishedAt: DateTime\n }\n\n type ToolCatalogEntry {\n name: String!\n label: String!\n description: String!\n category: String!\n effect: String!\n }\n\n """\n What was withheld from a catalogue, and why — REQ-045 (#204), task #210.\n\n Present only when a budget bound. A client rendering a shortened list with nothing saying it was shortened\n is the same invisible failure the run event exists to prevent, one layer out.\n """\n type ToolCatalogTruncation {\n budgetTokens: Int!\n residentTokens: Int!\n dropped: [String!]!\n "Whether find_tools is wired, which is the difference between a deferral and a removal."\n findable: Boolean!\n "True when the protected set alone exceeds the budget: a misconfiguration, not the mechanism working."\n overBudget: Boolean!\n }\n\n "The tenant's category switches as they were applied — task #210, AC-4."\n type TenantToolset {\n enabledCategories: [String!]\n disabledCategories: [String!]\n }\n\n type ToolCatalog {\n preloaded: [JSON!]!\n discoverable: [ToolCatalogEntry!]!\n meta: [ToolCatalogEntry!]!\n truncation: ToolCatalogTruncation\n toolset: TenantToolset\n }\n\n "A tool found by describing a need — task #210, AC-1. Filtered by the same authorization as discovery."\n type ToolSearchHit {\n entry: ToolCatalogEntry!\n score: Float!\n signals: [String!]!\n }\n\n type ToolSearchResult {\n hits: [ToolSearchHit!]!\n "The signals actually used. Keyword-only when no embedding provider is wired."\n modes: [String!]!\n }\n\n type UsageTotals {\n inputTokens: Int!\n outputTokens: Int!\n cachedInputTokens: Int!\n costMinorUnits: Int!\n eventCount: Int!\n }\n\n "One period's consumption. #139's rollups, so a chart never scans raw records."\n type UsageBucket {\n bucketStart: String!\n totals: UsageTotals!\n currency: String!\n }\n\n "Consumption grouped by model or conversation over the requested range."\n type UsageBreakdownEntry {\n key: String!\n totals: UsageTotals!\n }\n\n """\n Where the tenant stands against its limit.\n\n Absent when no limit is configured — which means unbounded, not zero. A UI must show "no limit set"\n rather than a full bar.\n """\n type UsageQuota {\n """\n The window in words — "the day", "any 5 hours" (#181).\n\n period stayed for the calendar case and is **null** for a rolling window, because no RollupPeriod describes\n one and returning "hour" for a five-hour window would be a wrong answer rather than a missing one. A client\n rendering the window reads this field; one keying a chart by bucket reads period and correctly finds nothing\n to key by.\n """\n window: String!\n period: String\n "The model this limit covers, or null for any model (#182)."\n modelId: String\n costLimitMinorUnits: Int\n inputTokenLimit: Int\n outputTokenLimit: Int\n "The fraction of the limit at which a warning shows. Sent so the UI cannot disagree with the server."\n warnAt: Float!\n "True once any dimension is past warnAt and still admitted."\n warning: Boolean!\n "True once any dimension has reached its limit, so work is being refused."\n exceeded: Boolean!\n }\n\n """\n The usage report a spend panel renders.\n\n One query rather than several, so a panel cannot show a total from one moment and a breakdown from another.\n """\n type UsageReport {\n period: String!\n from: String!\n to: String!\n totals: UsageTotals!\n buckets: [UsageBucket!]!\n byModel: [UsageBreakdownEntry!]!\n byConversation: [UsageBreakdownEntry!]!\n quota: UsageQuota\n currency: String!\n }\n\n "A single transport event; payload carries the typed part / lifecycle detail."\n type RunEvent {\n type: String!\n runId: ID!\n sequence: Int!\n occurredAt: DateTime!\n payload: JSON!\n }\n\n """\n One question put to a person, as it must be *rendered* — #163.\n\n The event that suspends a run carries only an interactionId, deliberately: events are thin, and a payload\n that duplicated the question would be a second copy to keep in step with the stored one. But nothing exposed\n the stored one either, so a client could answer a question it had no way to display. The example's picker\n rendered an empty text box next to "The assistant has a question", which is the whole gap in one screenshot.\n """\n type PendingQuestionSpec {\n "Stable key the answer is filed under."\n key: String!\n prompt: String!\n "A short closed list, when there is one. Empty means free text."\n options: [String!]!\n "Several choices are allowed, not one."\n multiple: Boolean!\n "Free text is accepted alongside the options."\n allowOther: Boolean!\n }\n\n type PendingQuestion {\n interactionId: ID!\n runId: ID!\n questions: [PendingQuestionSpec!]!\n createdAt: DateTime!\n }\n\n """\n The approval a run is parked on — the read side of decideApproval (#163).\n\n The same gap as pendingQuestion and milder rather than absent: approval.requested also carries only an\n interaction id, so a client had no summary to show and fell back to a generic "Run a tool?". Asking someone\n to authorise an action the card cannot name is how approval becomes a reflex.\n """\n type PendingApprovalDetail {\n interactionId: ID!\n runId: ID!\n toolName: String!\n "One line a person can decide on, written by the host's summarizer."\n summary: String!\n riskCategory: String!\n expiresAt: DateTime!\n "The arguments the approval is for, so what runs is what was shown."\n normalizedInput: JSON!\n }\n\n input QuestionAnswerInput {\n interactionId: ID!\n runId: ID!\n answers: JSON!\n }\n\n input ApprovalDecisionInput {\n interactionId: ID!\n runId: ID!\n decision: String!\n }\n\n "A section that shaped a turn's prompt — for the context inspector (#39)."\n type InspectedSection {\n title: String!\n providerId: String!\n kind: String!\n provenance: String!\n estimatedTokens: Int!\n sensitivity: String!\n included: Boolean!\n prunedReason: String\n }\n\n type ContextInspection {\n sections: [InspectedSection!]!\n totalTokens: Int!\n budget: JSON!\n }\n\n type Query {\n conversations(limit: Int!, cursor: String): ConversationPage!\n conversation(id: ID!): Conversation\n run(id: ID!): Run\n toolCatalog(preloaded: [String!]!, categories: [String!]!, excluded: [String!]!): ToolCatalog!\n """\n Search the catalogue by describing what you need — task #210, AC-1.\n\n Returns nothing at all when no search is configured, rather than failing: a client asking for a capability\n the deployment did not wire should render an empty result, not an error dialog.\n """\n findTools(query: String!, limit: Int): ToolSearchResult!\n usage(runId: ID): UsageTotals!\n """\n Consumption and cost by period, with breakdowns and quota state (#140).\n\n An extension of the usage query rather than a second endpoint: a panel showing a total from one query and a\n breakdown from another can show two moments at once, and the discrepancy looks like a bug in the numbers.\n """\n usageReport(period: String!, from: String!, to: String!, breakdownLimit: Int): UsageReport!\n "What context shaped a turn — attributes memory/tools/history that influenced the prompt."\n conversationContext(conversationId: ID!, runId: ID): ContextInspection\n """\n The question a run is parked on, or null — the read side of answerQuestion (#163).\n\n Null covers both "this run was never asked anything" and "it has been answered already", because a client\n has the same thing to do in either case: show no picker. A run that is waiting is the only state with a\n question to render.\n """\n pendingQuestion(runId: ID!): PendingQuestion\n "The approval a run is parked on, or null (#163)."\n pendingApproval(runId: ID!): PendingApprovalDetail\n }\n\n type Mutation {\n createConversation(id: ID!, title: String!): Conversation!\n renameConversation(id: ID!, expectedVersion: Int!, title: String!): Conversation!\n archiveConversation(id: ID!, expectedVersion: Int!): Conversation!\n deleteConversation(id: ID!): Boolean!\n sendMessage(conversationId: ID!, runId: ID!): Run!\n cancelRun(runId: ID!): Boolean!\n answerQuestion(input: QuestionAnswerInput!): Boolean!\n decideApproval(input: ApprovalDecisionInput!): Boolean!\n }\n\n type Subscription {\n "Conversation/run events, resumable after a cursor (sequence)."\n runEvents(runId: ID!, conversationId: ID!, after: Int): RunEvent!\n }\n"

Defined in: backend/src/graphql/schema.ts:11

GraphQL schema (SDL) — docs/06-graphql-and-frontend.md → GraphQL boundary.

The package ships the schema as SDL plus a thin resolver map (see ./resolvers), so a host can mount it on any GraphQL server (Yoga, Apollo, Mercurius) without the library taking a server dependency. Resolvers stay thin: authenticate, validate, build the execution context, call a platform service. Subscriptions carry the stable RunEvent set and support resuming after a cursor via openRunEventStream.