{"slug": "building-wan-3-0-the-hard-parts-of-an-ai-video-workspace", "title": "Building Wan 3.0: The Hard Parts of an AI Video Workspace", "summary": "The engineering team behind Wan 3.0, an AI video workspace, detailed the complex backend required to support text, image, frame, and reference-based video generation. They highlighted the use of explicit scene types, provider adapters, and a five-state task lifecycle to handle asynchronous generation, retries, and varying provider APIs. The team emphasized that early validation and normalized task records are crucial for reliability and auditability.", "body_md": "A prompt box makes an AI product look simple. The user writes a sentence,\n\nclicks **Generate**, and waits for a video.\n\nThe real system is less tidy. A request can outlive the browser tab, a provider\n\ncan accept a job and fail later, and a retry can accidentally create a second\n\nbillable task. Add multiple models, several input modes, and usage-based\n\npricing, and the prompt box becomes the smallest part of the product.\n\nThese are some of the engineering decisions behind\n\n[Wan 3.0](https://wan3.io), the AI video workspace we have been building for\n\ntext, image, frame, and reference-based generation. This is not a launch post\n\ndisguised as a tutorial. It is a practical look at the parts that took more\n\nthought than the interface suggests.\n\nText-to-video and image-to-video may end with the same file type, but they do\n\nnot begin with the same contract.\n\nA text request needs a prompt, aspect ratio, resolution, and duration. An\n\nimage-to-video request also needs an uploaded asset. A frame transition needs\n\ntwo ordered images, while reference-based generation may accept a clip or a\n\nset of visual references. Model support differs as well.\n\nWe represent those paths as explicit scenes rather than stretching one loose\n\npayload across every model:\n\n```\ntype VideoScene =\n  | 'text-to-video'\n  | 'image-to-video'\n  | 'frames-to-video'\n  | 'reference-to-video'\n  | 'video-edit'\n  | 'video-extend'\n  | 'video-upscale';\n```\n\nEach model declares the scenes and fields it supports. The UI can then adapt\n\nto the chosen workflow, and the server can reject combinations that do not\n\nmake sense before contacting a provider.\n\nThat early validation matters. An upstream API error is slower, harder to\n\nexplain, and sometimes more expensive than a local validation error.\n\nProvider APIs disagree about nearly everything: parameter names, callback\n\nformats, status values, result shapes, and whether polling or webhooks are the\n\npreferred completion path.\n\nLetting those differences leak into the product would couple every form and\n\ntask screen to a specific vendor. Instead, Wan 3.0 puts a small adapter around\n\neach provider:\n\n```\ninterface AIProvider {\n  readonly name: string;\n  readonly supportsWebhook: boolean;\n\n  generate(params: AIGenerateParams): Promise<AIProviderResult>;\n  query?(providerTaskId: string): Promise<AIProviderResult>;\n  verifyWebhook?(request: Request): Promise<AIProviderResult>;\n  cancel?(providerTaskId: string): Promise<void>;\n}\n```\n\nThe rest of the application works with one internal result shape and a small\n\nset of task states. Provider-specific code stays at the edge.\n\nThis does not make providers interchangeable. Models still have different\n\ninputs and capabilities. It does, however, give the application one place to\n\ntranslate those differences instead of scattering conditional logic across\n\nthe codebase.\n\nVideo generation is asynchronous by nature. Treating it like a normal request\n\nand keeping the browser waiting creates fragile behavior for both the user and\n\nthe server.\n\nOur task lifecycle uses five states:\n\n``` php\npending -> processing -> succeeded\n                      -> failed\n                      -> canceled\n```\n\nProviders can complete synchronously, through polling, or by webhook. Those\n\ntransport details are normalized into the same task record and result format.\n\nThe browser can leave, return later, and read the current state from history.\n\nThe simplified flow looks like this:\n\n```\nscene + model + inputs\n        |\n        v\nvalidation and credit estimate\n        |\n        v\ntask creation + credit reservation\n        |\n        v\nprovider adapter\n        |\n        v\nwebhook or polling\n        |\n        v\nresult history or automatic refund\n```\n\nPersisting the task also gives us a useful audit trail: selected model,\n\nprovider, normalized input, pricing snapshot, cost, timestamps, and terminal\n\nresult all belong to the same operation.\n\nRetries are normal. A user can double-click, the network can time out after\n\nthe server accepts a request, or a client can retry because it never received\n\nthe first response.\n\nFor a paid generation, “probably only once” is not good enough.\n\nEvery create request carries an idempotency key. We also calculate a\n\nfingerprint from the model, scene, and validated parameters. If the same key\n\nreturns with the same fingerprint, the existing task is reused. If that key\n\nappears with different input, the request is rejected.\n\nThe fingerprint check closes an easy-to-miss gap: an idempotency key should\n\nidentify one operation, not become a container for whichever payload arrives\n\nlast.\n\nUsage-based products should show the cost before the expensive operation\n\nstarts. The harder requirement is making sure the displayed estimate and the\n\nrecorded charge use the same calculation.\n\nWan 3.0 calculates credits from the selected model and validated settings.\n\nDepending on the model, duration, resolution, and other options can change the\n\nresult. The task stores both the calculated cost and a pricing snapshot, so a\n\nlater configuration change does not rewrite the meaning of an older task.\n\nTask insertion and credit reservation happen in one database transaction. If\n\neither step fails, neither should survive on its own. That keeps us away from\n\ntwo awkward states:\n\nThe UI benefit is straightforward: the number shown before submission is tied\n\nto the task the user sees afterward.\n\nExternal generation can fail after a provider has accepted the request. It can\n\nalso time out or send the same callback more than once. A refund handler must\n\ntherefore be safe to repeat.\n\nOur refund path locks the task record, checks whether the task is already in a\n\nterminal or refunded state, writes a refund transaction, restores the balance,\n\nand marks the task as refunded. All of that happens inside a transaction.\n\nThe goal is not “try to refund.” It is a narrower invariant:\n\nA failed generation that reserved credits can restore them once, even if the\n\nfailure signal is processed more than once.\n\nThis is one of those backend details that becomes a product feature. The user\n\ndoes not need to know about row locks or duplicate webhooks. They only need to\n\nsee that a failed render did not consume their balance.\n\nIf we started another asynchronous AI product tomorrow, we would define four\n\nthings before polishing the generation form:\n\nNone of these decisions produces a dramatic screenshot. Together, they make\n\nthe simple screenshot honest.\n\nWan 3.0 is a browser-based workspace for generating AI videos and images. Its\n\nvideo workflows include text, image, frame-pair, and reference inputs where\n\nthe selected model supports them.\n\nNo. Models declare their supported scenes and parameters. The interface and\n\nserver validation use that configuration to prevent unsupported combinations.\n\nIf a task reserved credits and later fails, the refund flow restores those\n\ncredits once and records the refund against the task.\n\nThe key identifies the operation. The fingerprint confirms that repeated uses\n\nof the key contain the same model, scene, and input. Together, they prevent a\n\nretry from silently becoming a different generation.\n\nBuilding an AI video workspace is mostly an exercise in managing uncertainty:\n\nslow jobs, changing providers, variable costs, duplicate requests, and partial\n\nfailures. A clean prompt box is valuable, but it only stays clean when the\n\ntask, provider, and credit systems underneath it have clear contracts.\n\nYou can try [Wan 3.0 at wan3.io](https://wan3.io). If you are building an\n\nasynchronous AI product, I would be interested to hear how you handle provider\n\ndrift, retries, and usage reconciliation.", "url": "https://wpnews.pro/news/building-wan-3-0-the-hard-parts-of-an-ai-video-workspace", "canonical_source": "https://dev.to/fei_gao_599260a6621676332/building-wan-30-the-hard-parts-of-an-ai-video-workspace-44a4", "published_at": "2026-08-24 09:00:12+00:00", "updated_at": "2026-08-24 09:13:25.901934+00:00", "lang": "en", "topics": ["artificial-intelligence", "generative-ai", "ai-products", "ai-infrastructure", "developer-tools"], "entities": ["Wan 3.0", "Wan 3.0 team"], "alternates": {"html": "https://wpnews.pro/news/building-wan-3-0-the-hard-parts-of-an-ai-video-workspace", "markdown": "https://wpnews.pro/news/building-wan-3-0-the-hard-parts-of-an-ai-video-workspace.md", "text": "https://wpnews.pro/news/building-wan-3-0-the-hard-parts-of-an-ai-video-workspace.txt", "jsonld": "https://wpnews.pro/news/building-wan-3-0-the-hard-parts-of-an-ai-video-workspace.jsonld"}}