{"record":{"id":"52dfc08bfd49d6ca","repo":"can1357/oh-my-pi","slug":"missing-context-object","errorCode":null,"errorMessage":"Missing `context` object","messagePattern":"Missing `context` object","errorType":"validation","errorClass":"AIError.ValidationError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/pi-native-server.ts","lineNumber":121,"sourceCode":"\tif (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n\t\tthrow new AIError.ValidationError(\"Request body must be a JSON object\");\n\t}\n\tconst obj = body as Record<string, unknown>;\n\n\tlet modelId: string | undefined;\n\tif (typeof obj.modelId === \"string\" && obj.modelId.length > 0) {\n\t\tmodelId = obj.modelId;\n\t} else if (typeof obj.model === \"string\" && obj.model.length > 0) {\n\t\tmodelId = obj.model;\n\t} else if (typeof obj.model === \"object\" && obj.model !== null) {\n\t\tconst m = obj.model as Record<string, unknown>;\n\t\tif (typeof m.id === \"string\" && m.id.length > 0) modelId = m.id;\n\t}\n\tif (!modelId) throw new AIError.ValidationError(\"Missing `modelId` (or `model.id`) field\");\n\n\tconst context = obj.context;\n\tif (typeof context !== \"object\" || context === null || Array.isArray(context)) {\n\t\tthrow new AIError.ValidationError(\"Missing `context` object\");\n\t}\n\tconst ctxObj = context as Record<string, unknown>;\n\tif (!Array.isArray(ctxObj.messages)) {\n\t\tthrow new AIError.ValidationError(\"`context.messages` must be an array\");\n\t}\n\tif (ctxObj.systemPrompt !== undefined && !Array.isArray(ctxObj.systemPrompt)) {\n\t\tthrow new AIError.ValidationError(\"`context.systemPrompt` must be an array of strings when present\");\n\t}\n\tif (ctxObj.tools !== undefined && !Array.isArray(ctxObj.tools)) {\n\t\tthrow new AIError.ValidationError(\"`context.tools` must be an array when present\");\n\t}\n\n\tconst options: SimpleStreamOptions = {};\n\tconst rawOpts = obj.options;\n\tif (typeof rawOpts === \"object\" && rawOpts !== null && !Array.isArray(rawOpts)) {\n\t\tconst optsBag = options as Record<string, unknown>;\n\t\tfor (const [k, v] of Object.entries(rawOpts)) {\n\t\t\tif (v === undefined || v === null) continue;","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/pi-native-server.ts#L103-L139","documentation":"parseRequest validates the JSON body of a request to the pi-native server before streaming. It requires a `context` object holding the conversation (messages, optional systemPrompt/tools). If `context` is absent, not an object, null, or an array, the server rejects the request with this ValidationError so callers get a clear 400-style signal instead of a downstream crash.","triggerScenarios":"POSTing a JSON body to the pi-native server endpoint whose top-level object lacks `context`, sets it to null, sets it to a string/number, or sends it as an array (e.g. `{\"model\":{\"id\":\"...\"}}` with no context key).","commonSituations":"Hand-written curl/httpie calls omitting the context wrapper; clients built against an older request schema that passed messages at the top level; JSON body accidentally stringified so `context` arrives as a string; proxies rewriting the body.","solutions":["Add a `context` object to the request body containing at least `messages` as an array","If migrating from an older schema, move top-level `messages`/`systemPrompt`/`tools` inside `context`","Check that the client is actually sending a parsed JSON object, not a JSON-encoded string as the body"],"exampleFix":"// before\n{\"model\":{\"id\":\"qwen3.5-plus\"},\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}\n// after\n{\"model\":{\"id\":\"qwen3.5-plus\"},\"context\":{\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}}","handlingStrategy":"validation","validationCode":"if (!body || typeof body !== 'object' || Array.isArray(body) || !body.context || typeof body.context !== 'object' || Array.isArray(body.context)) throw new Error('body.context object required');","typeGuard":"function hasContextObject(b: unknown): b is { context: Record<string, unknown> } {\n  return typeof b === 'object' && b !== null && !Array.isArray(b) && typeof (b as { context?: unknown }).context === 'object' && (b as { context: unknown }).context !== null && !Array.isArray((b as { context: unknown }).context);\n}","tryCatchPattern":"try { const req = parseRequest(body); } catch (e) { if (e instanceof AIError.ValidationError && String(e.message).includes('`context` object')) return respond400('request body must contain a context object'); throw e; }","preventionTips":["Keep a typed request-builder that always constructs {model, context:{messages,...}}","Unit-test the payload serializer against the server schema","Assert the body parses to an object (not a double-encoded string) before sending"],"tags":["validation","request-schema","http-server"],"backgroundTag":"missing-request-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}